minerender 2.1.0-alpha.114 → 2.1.0-alpha.116

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -280,7 +280,8 @@ var init_Caching = __esm({
280
280
  this.fontCache,
281
281
  this.bannerPatternCache,
282
282
  this.mineRenderDataCache,
283
- this.usernameUuidCache
283
+ this.usernameUuidCache,
284
+ this.biomeCache
284
285
  ];
285
286
  }
286
287
  /** Invalidates in-memory entries so later lookups reload them. Persisted entries are retained. */
@@ -331,6 +332,7 @@ var init_Caching = __esm({
331
332
  _Caching.bannerPatternCache = _Caching.createAssetCache();
332
333
  _Caching.mineRenderDataCache = _Caching.createAssetCache();
333
334
  _Caching.usernameUuidCache = import_loading_cache.Caches.builder().expireAfterWrite(import_time.Time.minutes(5)).expirationInterval(import_time.Time.seconds(30)).buildAsync();
335
+ _Caching.biomeCache = _Caching.createAssetCache();
334
336
  Caching = _Caching;
335
337
  }
336
338
  });
@@ -1677,7 +1679,7 @@ ${key}` : key, async () => {
1677
1679
  case "blockStates":
1678
1680
  return Object.values(value).every((property) => object(property) && "default" in property && Array.isArray(property.values));
1679
1681
  case "blockTints":
1680
- return ["constant", "grass", "stem", "redstone"].includes(value.source) && (value.source !== "constant" || Number.isInteger(value.color));
1682
+ 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));
1681
1683
  case "fluids":
1682
1684
  return ["water", "lava"].includes(value.kind) && typeof value.renderModel === "boolean" && (value.levelProperty === void 0 || typeof value.levelProperty === "string");
1683
1685
  case "dyes":
@@ -1821,15 +1823,19 @@ var init_Colormaps = __esm({
1821
1823
  Colormaps = class {
1822
1824
  /** Samples the selected resource pack's grass colormap using Minecraft's climate coordinates. */
1823
1825
  static async grassColor(origin, temperature = 0.5, downfall = 1) {
1824
- const key = AssetKey.parse("textures", "minecraft:colormap/grass", origin);
1826
+ return this.getColor("grass", origin, temperature, downfall);
1827
+ }
1828
+ /** Samples a resource-pack colormap at the supplied climate coordinates. */
1829
+ static async getColor(kind, origin, temperature = 0.5, downfall = 1) {
1830
+ const key = AssetKey.parse("textures", `minecraft:colormap/${kind}`, origin);
1825
1831
  const temp = Math.fround(temperature);
1826
1832
  const rain = Math.fround(downfall) * temp;
1827
1833
  const x = Math.floor((1 - temp) * 255), y = Math.floor((1 - rain) * 255);
1828
1834
  return await Caching.blockTintCache.get(`${key.serialize()}|${x},${y}`, async () => {
1829
1835
  const image = await ModelTextures.get(key);
1830
- if (!image) throw new MineRenderError(`Missing grass colormap: ${key.toNamespacedString()}`);
1836
+ if (!image) throw new MineRenderError(`Missing ${kind} colormap: ${key.toNamespacedString()}`);
1831
1837
  if (image.width !== 256 || image.height !== 256) {
1832
- throw new MineRenderError("Grass colormaps must be 256 \xD7 256 pixels");
1838
+ throw new MineRenderError(`${kind} colormaps must be 256 \xD7 256 pixels`);
1833
1839
  }
1834
1840
  const [red, green, blue] = image.data.getImageData(x, y, 1, 1).data;
1835
1841
  return red << 16 | green << 8 | blue;
@@ -7516,6 +7522,7 @@ __export(index_browser_exports, {
7516
7522
  BannerPatterns: () => BannerPatterns,
7517
7523
  BasicAssetKey: () => BasicAssetKey,
7518
7524
  BatchedExecutor: () => BatchedExecutor,
7525
+ Biomes: () => Biomes,
7519
7526
  BlockEntities: () => BlockEntities,
7520
7527
  BlockObject: () => BlockObject,
7521
7528
  BlockStateResolver: () => BlockStateResolver,
@@ -7549,6 +7556,7 @@ __export(index_browser_exports, {
7549
7556
  EntityObject: () => EntityObject,
7550
7557
  Env: () => Env,
7551
7558
  FluidModelObject: () => FluidModelObject,
7559
+ FlyControls: () => FlyControls,
7552
7560
  Fonts: () => Fonts,
7553
7561
  GUI_CONTAINER_LAYOUTS: () => GUI_CONTAINER_LAYOUTS,
7554
7562
  Geometries: () => Geometries,
@@ -7651,6 +7659,7 @@ __export(index_browser_exports, {
7651
7659
  isDisposable: () => isDisposable,
7652
7660
  isDoubleArray: () => isDoubleArray,
7653
7661
  isEntityObject: () => isEntityObject,
7662
+ isFlyControls: () => isFlyControls,
7654
7663
  isGuiObject: () => isGuiObject,
7655
7664
  isInstanceManager: () => isInstanceManager,
7656
7665
  isInstanceReference: () => isInstanceReference,
@@ -7903,6 +7912,116 @@ init_AssetLoader();
7903
7912
  init_Assets();
7904
7913
  init_BannerPatterns();
7905
7914
 
7915
+ // src/assets/Biomes.ts
7916
+ init_AssetKey();
7917
+ init_AssetLoader();
7918
+ init_AssetParsers();
7919
+ init_Caching();
7920
+ init_MineRenderError();
7921
+ init_Colormaps();
7922
+
7923
+ // src/assets/_biomes/SwampGrassColor.ts
7924
+ var permutation;
7925
+ var gradients = [[1, 1], [-1, 1], [1, -1], [-1, -1], [1, 0], [-1, 0], [1, 0], [-1, 0], [0, 1], [0, -1], [0, 1], [0, -1]];
7926
+ var skew = (Math.sqrt(3) - 1) / 2;
7927
+ var unskew = (3 - Math.sqrt(3)) / 6;
7928
+ function createPermutation() {
7929
+ const multiplier = 0x5deece66dn, mask = (1n << 48n) - 1n;
7930
+ let seed = 2345n ^ multiplier;
7931
+ const next = (bits) => {
7932
+ seed = seed * multiplier + 11n & mask;
7933
+ return Number(seed >> BigInt(48 - bits));
7934
+ };
7935
+ for (let i = 0; i < 3; i++) {
7936
+ next(26);
7937
+ next(27);
7938
+ }
7939
+ const values = Uint8Array.from({ length: 256 }, (_, i) => i);
7940
+ for (let i = 0; i < 256; i++) {
7941
+ const bound = 256 - i;
7942
+ let offset;
7943
+ if ((bound & bound - 1) === 0) offset = Math.floor(bound * next(31) / 2147483648);
7944
+ else {
7945
+ let bits;
7946
+ do {
7947
+ bits = next(31);
7948
+ offset = bits % bound;
7949
+ } while (bits - offset + bound - 1 > 2147483647);
7950
+ }
7951
+ const other = i + offset;
7952
+ [values[i], values[other]] = [values[other], values[i]];
7953
+ }
7954
+ return values;
7955
+ }
7956
+ function swampGrassColor(x, z) {
7957
+ const p10 = permutation ?? (permutation = createPermutation());
7958
+ x *= 0.0225;
7959
+ z *= 0.0225;
7960
+ const s = (x + z) * skew;
7961
+ const i = Math.floor(x + s), j = Math.floor(z + s);
7962
+ const t = (i + j) * unskew;
7963
+ const dx = x - (i - t), dz = z - (j - t);
7964
+ const stepX = dx > dz ? 1 : 0, stepZ = 1 - stepX;
7965
+ const corner = (gx, gz, a, b) => {
7966
+ let falloff = 0.5 - a * a - b * b;
7967
+ if (falloff < 0) return 0;
7968
+ const gradient = gradients[p10[gx + p10[gz & 255] & 255] % 12];
7969
+ falloff *= falloff;
7970
+ return falloff * falloff * (gradient[0] * a + gradient[1] * b);
7971
+ };
7972
+ const noise = 70 * (corner(i, j, dx, dz) + corner(i + stepX, j + stepZ, dx - stepX + unskew, dz - stepZ + unskew) + corner(i + 1, j + 1, dx - 1 + 2 * unskew, dz - 1 + 2 * unskew));
7973
+ return noise < -0.1 ? 5011004 : 6975545;
7974
+ }
7975
+
7976
+ // src/assets/Biomes.ts
7977
+ var Biomes = class {
7978
+ /** Loads a biome registry entry, or returns `undefined` when no source provides it. */
7979
+ static async get(id, origin) {
7980
+ if (!/^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(id)) throw new MineRenderError(`Invalid biome identifier: ${id}`);
7981
+ const [namespace, path] = id.includes(":") ? id.split(":") : ["minecraft", id];
7982
+ const key = new AssetKey(namespace, path, "worldgen", "biome", "data", ".json", origin?.root);
7983
+ return Caching.biomeCache.get(key.serialize(), async () => {
7984
+ const asset2 = await AssetLoader.get(key, "json" /* JSON */);
7985
+ if (asset2 === void 0) return void 0;
7986
+ if (!asset2 || typeof asset2 !== "object" || Array.isArray(asset2) || !asset2.effects || typeof asset2.effects !== "object" || Array.isArray(asset2.effects)) {
7987
+ throw new MineRenderError(`Invalid biome effects: ${id}`);
7988
+ }
7989
+ if (typeof asset2.temperature !== "number" || !Number.isFinite(asset2.temperature) || typeof asset2.downfall !== "number" || !Number.isFinite(asset2.downfall)) {
7990
+ throw new MineRenderError(`Invalid biome climate: ${id}`);
7991
+ }
7992
+ for (const kind of ["grass", "foliage", "dry_foliage", "water"]) {
7993
+ const color2 = asset2.effects[`${kind}_color`];
7994
+ if (color2 !== void 0) this.parseColor(color2, id, kind);
7995
+ }
7996
+ const modifier = asset2.effects.grass_color_modifier;
7997
+ if (modifier !== void 0 && modifier !== "none" && modifier !== "dark_forest" && modifier !== "swamp") {
7998
+ throw new MineRenderError(`Invalid biome grass color modifier: ${id}`);
7999
+ }
8000
+ return asset2;
8001
+ });
8002
+ }
8003
+ /** Returns packed sRGB, or `undefined` when the biome or requested water color is missing. */
8004
+ static async getColor(id, kind, origin, x = 0, z = 0) {
8005
+ const biome = await this.get(id, origin);
8006
+ if (!biome) return void 0;
8007
+ const rawColor = biome.effects[`${kind}_color`];
8008
+ let color2 = rawColor === void 0 ? void 0 : this.parseColor(rawColor, id, kind);
8009
+ if (kind === "water") return color2;
8010
+ const modifier = kind === "grass" ? biome.effects.grass_color_modifier : void 0;
8011
+ if (modifier === "swamp") return swampGrassColor(x, z);
8012
+ if (color2 === void 0) {
8013
+ const clamp = (value) => Math.max(0, Math.min(1, Math.fround(value)));
8014
+ color2 = await Colormaps.getColor(kind, origin, clamp(biome.temperature), clamp(biome.downfall));
8015
+ }
8016
+ return modifier === "dark_forest" ? (color2 & 16711422) + 2634762 >> 1 : color2;
8017
+ }
8018
+ static parseColor(value, id, kind) {
8019
+ if (typeof value === "string" && /^#[0-9a-fA-F]{6}$/.test(value)) return parseInt(value.slice(1), 16);
8020
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 16777215) return value;
8021
+ throw new MineRenderError(`Invalid biome ${kind} color: ${id}`);
8022
+ }
8023
+ };
8024
+
7906
8025
  // src/assets/BlockEntities.ts
7907
8026
  var import_three10 = require("three");
7908
8027
  init_AssetKey();
@@ -12341,8 +12460,20 @@ var BlockStateResolver = class _BlockStateResolver {
12341
12460
  init_Colormaps();
12342
12461
  init_MineRenderData();
12343
12462
  var BlockTints = class {
12344
- /** Resolves block preview colors without biome context; explicit tints take precedence. */
12345
- static async get(key, state, model, tints) {
12463
+ /** Returns a block's versioned biome color source, leaving fixed-color blocks unchanged. */
12464
+ static async biomeSource(key) {
12465
+ const rule = key && (await MineRenderData.get("blockTints", key.root))[key.toNamespacedString()];
12466
+ return rule ? rule.biome ?? (rule.source === "grass" ? "grass" : void 0) : void 0;
12467
+ }
12468
+ /** Resolves one block's biome tint at world block coordinates, without biome blending. */
12469
+ static async getBiomeColor(key, biome, position) {
12470
+ if (!biome) return void 0;
12471
+ const x = position?.[0], z = position?.[2];
12472
+ const source = await this.biomeSource(key);
12473
+ return source ? Biomes.getColor(biome, source, key, x, z) : void 0;
12474
+ }
12475
+ /** Resolves used tint indices from biome or preview colors; explicit tints take precedence. */
12476
+ static async get(key, state, model, tints, biomeColor) {
12346
12477
  if (!key) return tints;
12347
12478
  const candidates = (model.elements ?? []).flatMap((element) => Object.values(element.faces)).some((face) => face?.tintindex !== void 0 && face.tintindex >= 0 && tints?.[face.tintindex] === void 0);
12348
12479
  if (!candidates) return tints;
@@ -12358,8 +12489,12 @@ var BlockTints = class {
12358
12489
  }
12359
12490
  }
12360
12491
  if (!indices.size) return tints;
12492
+ const source = rule.biome ?? (rule.source === "grass" ? "grass" : void 0);
12493
+ const biome = source ? typeof biomeColor === "function" ? await biomeColor() : biomeColor : void 0;
12361
12494
  let color2;
12362
- switch (rule.source) {
12495
+ if (biome !== void 0) {
12496
+ color2 = biome;
12497
+ } else switch (rule.source) {
12363
12498
  case "constant":
12364
12499
  color2 = rule.color;
12365
12500
  break;
@@ -12418,6 +12553,7 @@ var _BlockObject = class _BlockObject extends SceneObject {
12418
12553
  this._entityGeneration = 0;
12419
12554
  this.options = (0, import_ts_deepmerge6.default)({}, _BlockObject.DEFAULT_OPTIONS, options ?? {});
12420
12555
  this.variantPosition = options?.variantPosition ? [...options.variantPosition] : void 0;
12556
+ this.biomePosition = options?.biomePosition ? [...options.biomePosition] : void 0;
12421
12557
  }
12422
12558
  async init() {
12423
12559
  const root = AssetLoader.ROOT, scope = AssetLoader.persistentScope;
@@ -12610,11 +12746,16 @@ var _BlockObject = class _BlockObject extends SceneObject {
12610
12746
  const { FluidModelObject: FluidModelObject2, sampleFluid: sampleFluid2 } = await Promise.resolve().then(() => (init_FluidModelObject(), FluidModelObject_exports));
12611
12747
  this._fluidSampler = sample;
12612
12748
  const surface = sampleFluid2(kind, (x, y, z) => x === 0 && y === 0 && z === 0 ? { fluid: kind, level: this.fluidLevel } : sample?.(x, y, z) ?? {});
12613
- if (this._fluidKey === surface.key) return;
12749
+ const tint = this.options.tints?.[0] ?? this.waterTint;
12750
+ const fluidKey = `${surface.key}|${tint ?? "default"}`;
12751
+ if (this._fluidKey === fluidKey) return;
12614
12752
  const key = new AssetKey("minecraft", `${kind}/${surface.key}`, "models", "fluid", "assets", ".json", this.blockState.key?.root);
12615
12753
  const replacement = await this.scene.addSceneObject(
12616
12754
  { key },
12617
- () => new FluidModelObject2(kind, surface.sample, this.blockState.key, this.options)
12755
+ () => new FluidModelObject2(kind, surface.sample, this.blockState.key, {
12756
+ ...this.options,
12757
+ tints: tint === void 0 ? this.options.tints : { ...this.options.tints, 0: tint }
12758
+ })
12618
12759
  );
12619
12760
  const previous = this._fluidModel;
12620
12761
  const matrix = previous ? this.getModelMatrix(previous) : new import_three34.Matrix4().makeTranslation(...this.position.toArray());
@@ -12623,7 +12764,7 @@ var _BlockObject = class _BlockObject extends SceneObject {
12623
12764
  this._models = this._models.filter((model) => model !== previous);
12624
12765
  this._models.push(replacement);
12625
12766
  this._fluidModel = replacement;
12626
- this._fluidKey = surface.key;
12767
+ this._fluidKey = fluidKey;
12627
12768
  this._isInstanced || (this._isInstanced = isInstanceReference(replacement));
12628
12769
  this._instanceCounter = this._isInstanced ? 1 : 0;
12629
12770
  if (previous) this.removeModels([previous]);
@@ -12646,6 +12787,7 @@ var _BlockObject = class _BlockObject extends SceneObject {
12646
12787
  }
12647
12788
  async recreateModels() {
12648
12789
  this.clearModels();
12790
+ this.waterTint = this.fluidKind === "water" && this.options.biome && this.options.tints?.[0] === void 0 ? await Biomes.getColor(this.options.biome, "water", this.blockState.key) : void 0;
12649
12791
  if (resolveBlockFluidState(this.blockState.key, this.state, this._fluidRules)?.renderModel === false) {
12650
12792
  await this.updateFluid(this._fluidSampler);
12651
12793
  return;
@@ -12680,7 +12822,13 @@ var _BlockObject = class _BlockObject extends SceneObject {
12680
12822
  const model = await Models.getMerged(modelKey);
12681
12823
  const options = {
12682
12824
  ...this.options,
12683
- tints: model ? await BlockTints.get(this.blockState.key, this.state, model, this.options.tints) : this.options.tints,
12825
+ tints: model ? await BlockTints.get(
12826
+ this.blockState.key,
12827
+ this.state,
12828
+ model,
12829
+ this.options.tints,
12830
+ () => BlockTints.getBiomeColor(this.blockState.key, this.options.biome, this.biomePosition)
12831
+ ) : this.options.tints,
12684
12832
  uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0) ? [rotation.x, rotation.y, rotation.z] : void 0,
12685
12833
  cullMask: this.options.displayPosition ? 0 : ModelCulling.toLocalMask(this._cullMask, rotation)
12686
12834
  };
@@ -13267,8 +13415,323 @@ init_ModelObject();
13267
13415
  // src/index.ts
13268
13416
  init_nbt();
13269
13417
 
13270
- // src/renderer/MineRenderScene.ts
13418
+ // src/renderer/FlyControls.ts
13271
13419
  var import_three35 = require("three");
13420
+ var VANILLA_FLY_SPEED = 10.89;
13421
+ var PITCH_LIMIT = Math.PI / 2;
13422
+ var MOVE_EPSILON = 1e-6;
13423
+ var _euler = new import_three35.Euler(0, 0, 0, "YXZ");
13424
+ var _forward = new import_three35.Vector3();
13425
+ var _right = new import_three35.Vector3();
13426
+ var _targetVelocity = new import_three35.Vector3();
13427
+ var FlyControls = class extends import_three35.Controls {
13428
+ constructor(camera, domElement) {
13429
+ super(camera, domElement ?? null);
13430
+ this.isFlyControls = true;
13431
+ /** Horizontal and vertical speed in scene units per second; vanilla creative flight by default. */
13432
+ this.movementSpeed = 16 * VANILLA_FLY_SPEED;
13433
+ /** Speed multiplier while the sprint key is held. */
13434
+ this.sprintMultiplier = 2;
13435
+ /** Radians per pixel of pointer movement. */
13436
+ this.lookSpeed = 2e-3;
13437
+ /** Smooth acceleration and deceleration, approximating vanilla flight momentum. */
13438
+ this.enableDamping = true;
13439
+ /** Portion of the remaining velocity change applied per 60 Hz frame (0–1) when damping is enabled. */
13440
+ this.dampingFactor = 0.25;
13441
+ /** Capture the pointer on click so the mouse turns the camera without dragging; dragging works regardless. */
13442
+ this.pointerLock = true;
13443
+ /** Scroll the wheel to change `movementSpeed` within `speedRange`. */
13444
+ this.enableWheelSpeed = true;
13445
+ /** Minimum and maximum `movementSpeed` reachable with the wheel. */
13446
+ this.speedRange = [16, 16 * 500];
13447
+ /** Touch joystick travel in CSS pixels for full speed. */
13448
+ this.joystickRadius = 64;
13449
+ /** Key bindings by `KeyboardEvent.code`. */
13450
+ this.keys = {
13451
+ forward: ["KeyW", "ArrowUp"],
13452
+ back: ["KeyS", "ArrowDown"],
13453
+ left: ["KeyA", "ArrowLeft"],
13454
+ right: ["KeyD", "ArrowRight"],
13455
+ up: ["Space"],
13456
+ down: ["ShiftLeft", "ShiftRight"],
13457
+ sprint: ["ControlLeft", "ControlRight"]
13458
+ };
13459
+ this._pressed = /* @__PURE__ */ new Set();
13460
+ this._velocity = new import_three35.Vector3();
13461
+ this._lastPosition = new import_three35.Vector3();
13462
+ this._lastQuaternion = new import_three35.Quaternion();
13463
+ this._position0 = new import_three35.Vector3();
13464
+ this._quaternion0 = new import_three35.Quaternion();
13465
+ this._dragMoved = false;
13466
+ this._locked = false;
13467
+ this._onKeyDown = (event) => this.handleKey(event, true);
13468
+ this._onKeyUp = (event) => this.handleKey(event, false);
13469
+ this._onPointerDown = (event) => this.handlePointerDown(event);
13470
+ this._onPointerMove = (event) => this.handlePointerMove(event);
13471
+ this._onPointerUp = (event) => this.handlePointerUp(event);
13472
+ this._onPointerLockChange = () => this.handlePointerLockChange();
13473
+ this._onContextMenu = (event) => event.preventDefault();
13474
+ this._onWheel = (event) => this.handleWheel(event);
13475
+ this._onBlur = () => this.releaseKeys();
13476
+ this._lastPosition.copy(camera.position);
13477
+ this._lastQuaternion.copy(camera.quaternion);
13478
+ this.saveState();
13479
+ if (domElement) this.connect(domElement);
13480
+ }
13481
+ connect(element) {
13482
+ super.connect(element);
13483
+ const document2 = element.ownerDocument;
13484
+ document2.addEventListener("keydown", this._onKeyDown);
13485
+ document2.addEventListener("keyup", this._onKeyUp);
13486
+ document2.addEventListener("pointerlockchange", this._onPointerLockChange);
13487
+ document2.defaultView?.addEventListener("blur", this._onBlur);
13488
+ element.addEventListener("pointerdown", this._onPointerDown);
13489
+ element.addEventListener("pointermove", this._onPointerMove);
13490
+ element.addEventListener("pointerup", this._onPointerUp);
13491
+ element.addEventListener("pointercancel", this._onPointerUp);
13492
+ element.addEventListener("contextmenu", this._onContextMenu);
13493
+ element.addEventListener("wheel", this._onWheel, { passive: false });
13494
+ element.addEventListener("blur", this._onBlur);
13495
+ element.style.touchAction = "none";
13496
+ if (element.tabIndex < 0) element.tabIndex = 0;
13497
+ }
13498
+ disconnect() {
13499
+ const element = this.domElement;
13500
+ if (!element) return;
13501
+ const document2 = element.ownerDocument;
13502
+ document2.removeEventListener("keydown", this._onKeyDown);
13503
+ document2.removeEventListener("keyup", this._onKeyUp);
13504
+ document2.removeEventListener("pointerlockchange", this._onPointerLockChange);
13505
+ document2.defaultView?.removeEventListener("blur", this._onBlur);
13506
+ element.removeEventListener("pointerdown", this._onPointerDown);
13507
+ element.removeEventListener("pointermove", this._onPointerMove);
13508
+ element.removeEventListener("pointerup", this._onPointerUp);
13509
+ element.removeEventListener("pointercancel", this._onPointerUp);
13510
+ element.removeEventListener("contextmenu", this._onContextMenu);
13511
+ element.removeEventListener("wheel", this._onWheel);
13512
+ element.removeEventListener("blur", this._onBlur);
13513
+ element.style.touchAction = "auto";
13514
+ this.unlock();
13515
+ this._locked = false;
13516
+ this.releaseKeys();
13517
+ this._dragPointer = void 0;
13518
+ this._joystick = void 0;
13519
+ }
13520
+ dispose() {
13521
+ this.disconnect();
13522
+ }
13523
+ /** Whether mouse look currently owns the pointer. */
13524
+ get locked() {
13525
+ return this._locked;
13526
+ }
13527
+ /** Requests pointer capture; browsers only allow this from a user gesture. */
13528
+ lock() {
13529
+ const element = this.domElement;
13530
+ if (!element || this._locked || typeof element.requestPointerLock !== "function") return;
13531
+ try {
13532
+ const result = element.requestPointerLock();
13533
+ if (result instanceof Promise) result.catch(() => void 0);
13534
+ } catch {
13535
+ }
13536
+ }
13537
+ /** Releases a captured pointer. */
13538
+ unlock() {
13539
+ const document2 = this.domElement?.ownerDocument;
13540
+ if (document2?.pointerLockElement && document2.pointerLockElement === this.domElement) {
13541
+ document2.exitPointerLock();
13542
+ }
13543
+ }
13544
+ /** Stores the camera pose that {@link reset} restores. */
13545
+ saveState() {
13546
+ this._position0.copy(this.object.position);
13547
+ this._quaternion0.copy(this.object.quaternion);
13548
+ }
13549
+ /** Restores the saved camera pose and stops movement. */
13550
+ reset() {
13551
+ this.object.position.copy(this._position0);
13552
+ this.object.quaternion.copy(this._quaternion0);
13553
+ this._velocity.set(0, 0, 0);
13554
+ this.notifyChange();
13555
+ }
13556
+ /** Horizontal yaw in radians, where zero looks toward -Z. */
13557
+ get yaw() {
13558
+ return _euler.setFromQuaternion(this.object.quaternion).y;
13559
+ }
13560
+ /** Vertical pitch in radians, positive looking up. */
13561
+ get pitch() {
13562
+ return _euler.setFromQuaternion(this.object.quaternion).x;
13563
+ }
13564
+ /** Turns the camera; pitch is clamped to straight up and down. */
13565
+ setRotation(yaw, pitch) {
13566
+ _euler.set(import_three35.MathUtils.clamp(pitch, -PITCH_LIMIT, PITCH_LIMIT), yaw, 0);
13567
+ this.object.quaternion.setFromEuler(_euler);
13568
+ this.notifyChange();
13569
+ }
13570
+ /** Points the camera at a position without rolling it. */
13571
+ lookAt(target) {
13572
+ _forward.copy(target).sub(this.object.position);
13573
+ if (_forward.lengthSq() < MOVE_EPSILON) return;
13574
+ _forward.normalize();
13575
+ this.setRotation(Math.atan2(-_forward.x, -_forward.z), Math.asin(import_three35.MathUtils.clamp(_forward.y, -1, 1)));
13576
+ }
13577
+ /** Whether an action's key is held. */
13578
+ isPressed(action) {
13579
+ return this._pressed.has(action);
13580
+ }
13581
+ /**
13582
+ * Advances movement by `delta` seconds (measured since the previous call when omitted)
13583
+ * and dispatches `change` when the camera moved.
13584
+ */
13585
+ update(delta) {
13586
+ if (delta === void 0) {
13587
+ const now = typeof performance !== "undefined" ? performance.now() : Date.now();
13588
+ delta = this._lastTime === void 0 ? 0 : (now - this._lastTime) / 1e3;
13589
+ this._lastTime = now;
13590
+ }
13591
+ if (!this.enabled) {
13592
+ this._velocity.set(0, 0, 0);
13593
+ return;
13594
+ }
13595
+ if (delta > 0) this.move(delta);
13596
+ this.notifyChange();
13597
+ }
13598
+ move(delta) {
13599
+ const camera = this.object;
13600
+ let forward = (this._pressed.has("forward") ? 1 : 0) - (this._pressed.has("back") ? 1 : 0);
13601
+ let strafe = (this._pressed.has("right") ? 1 : 0) - (this._pressed.has("left") ? 1 : 0);
13602
+ const climb = (this._pressed.has("up") ? 1 : 0) - (this._pressed.has("down") ? 1 : 0);
13603
+ const joystick = this._joystick;
13604
+ if (joystick) {
13605
+ const radius = Math.max(this.joystickRadius, 1);
13606
+ forward = import_three35.MathUtils.clamp(forward + (joystick.originY - joystick.y) / radius, -1, 1);
13607
+ strafe = import_three35.MathUtils.clamp(strafe + (joystick.x - joystick.originX) / radius, -1, 1);
13608
+ }
13609
+ _euler.setFromQuaternion(camera.quaternion);
13610
+ const yaw = _euler.y;
13611
+ _forward.set(-Math.sin(yaw), 0, -Math.cos(yaw));
13612
+ _right.set(Math.cos(yaw), 0, -Math.sin(yaw));
13613
+ _targetVelocity.set(0, 0, 0);
13614
+ if (forward || strafe || climb) {
13615
+ _targetVelocity.addScaledVector(_forward, forward).addScaledVector(_right, strafe);
13616
+ if (_targetVelocity.lengthSq() > 1) _targetVelocity.normalize();
13617
+ _targetVelocity.y = climb;
13618
+ const speed = this.movementSpeed * (this._pressed.has("sprint") ? this.sprintMultiplier : 1);
13619
+ _targetVelocity.multiplyScalar(speed);
13620
+ }
13621
+ if (this.enableDamping) {
13622
+ const blend = 1 - Math.pow(1 - import_three35.MathUtils.clamp(this.dampingFactor, 0, 1), delta * 60);
13623
+ this._velocity.lerp(_targetVelocity, blend);
13624
+ if (_targetVelocity.lengthSq() === 0 && this._velocity.lengthSq() < MOVE_EPSILON) this._velocity.set(0, 0, 0);
13625
+ } else {
13626
+ this._velocity.copy(_targetVelocity);
13627
+ }
13628
+ camera.position.addScaledVector(this._velocity, delta);
13629
+ }
13630
+ notifyChange() {
13631
+ const camera = this.object;
13632
+ if (this._lastPosition.distanceToSquared(camera.position) > MOVE_EPSILON || 8 * (1 - this._lastQuaternion.dot(camera.quaternion)) > MOVE_EPSILON) {
13633
+ this._lastPosition.copy(camera.position);
13634
+ this._lastQuaternion.copy(camera.quaternion);
13635
+ this.dispatchEvent({ type: "change" });
13636
+ }
13637
+ }
13638
+ releaseKeys() {
13639
+ this._pressed.clear();
13640
+ }
13641
+ handleKey(event, down) {
13642
+ const action = this.actionFor(event.code);
13643
+ if (!action) return;
13644
+ const ownsInput = this.enabled && (this._locked || event.target === this.domElement);
13645
+ if (down && !ownsInput) return;
13646
+ const pressed = this._pressed.has(action);
13647
+ if (down) this._pressed.add(action);
13648
+ else this._pressed.delete(action);
13649
+ if (ownsInput || this.enabled && pressed) event.preventDefault();
13650
+ }
13651
+ actionFor(code) {
13652
+ for (const action in this.keys) {
13653
+ if (this.keys[action].includes(code)) return action;
13654
+ }
13655
+ return void 0;
13656
+ }
13657
+ handlePointerDown(event) {
13658
+ const element = this.domElement;
13659
+ if (!this.enabled || !element) return;
13660
+ element.focus?.({ preventScroll: true });
13661
+ if (this._locked) return;
13662
+ if (event.pointerType === "touch" && !this._joystick && this.onJoystickSide(event)) {
13663
+ this._joystick = { id: event.pointerId, originX: event.clientX, originY: event.clientY, x: event.clientX, y: event.clientY };
13664
+ element.setPointerCapture?.(event.pointerId);
13665
+ return;
13666
+ }
13667
+ if (this._dragPointer !== void 0) return;
13668
+ if (event.pointerType === "mouse" && event.button !== 0 && event.button !== 2) return;
13669
+ this._dragPointer = event.pointerId;
13670
+ this._dragMoved = false;
13671
+ element.setPointerCapture?.(event.pointerId);
13672
+ }
13673
+ onJoystickSide(event) {
13674
+ const rect = this.domElement?.getBoundingClientRect?.();
13675
+ return !!rect && event.clientX < rect.left + rect.width / 2;
13676
+ }
13677
+ handlePointerMove(event) {
13678
+ if (!this.enabled) return;
13679
+ const joystick = this._joystick;
13680
+ if (joystick && event.pointerId === joystick.id) {
13681
+ joystick.x = event.clientX;
13682
+ joystick.y = event.clientY;
13683
+ return;
13684
+ }
13685
+ if (!this._locked && event.pointerId !== this._dragPointer) return;
13686
+ const dx = event.movementX ?? 0;
13687
+ const dy = event.movementY ?? 0;
13688
+ if (!dx && !dy) return;
13689
+ this._dragMoved = true;
13690
+ this.turn(dx, dy);
13691
+ }
13692
+ handlePointerUp(event) {
13693
+ if (this._joystick?.id === event.pointerId) {
13694
+ this._joystick = void 0;
13695
+ this.domElement?.releasePointerCapture?.(event.pointerId);
13696
+ return;
13697
+ }
13698
+ if (event.pointerId !== this._dragPointer) return;
13699
+ this._dragPointer = void 0;
13700
+ this.domElement?.releasePointerCapture?.(event.pointerId);
13701
+ if (this.enabled && this.pointerLock && !this._dragMoved && event.pointerType === "mouse" && event.button === 0) {
13702
+ this.lock();
13703
+ }
13704
+ }
13705
+ handleWheel(event) {
13706
+ if (!this.enabled || !this.enableWheelSpeed) return;
13707
+ event.preventDefault();
13708
+ const steps = event.deltaMode === 0 ? event.deltaY / 100 : event.deltaY;
13709
+ const [min, max] = this.speedRange;
13710
+ this.movementSpeed = import_three35.MathUtils.clamp(this.movementSpeed * Math.pow(1.15, -steps), Math.min(min, max), Math.max(min, max));
13711
+ }
13712
+ handlePointerLockChange() {
13713
+ const document2 = this.domElement?.ownerDocument;
13714
+ const locked = !!document2 && document2.pointerLockElement === this.domElement;
13715
+ if (locked === this._locked) return;
13716
+ this._locked = locked;
13717
+ if (!locked) this.releaseKeys();
13718
+ this.dispatchEvent({ type: locked ? "lock" : "unlock" });
13719
+ }
13720
+ turn(dx, dy) {
13721
+ _euler.setFromQuaternion(this.object.quaternion);
13722
+ _euler.y -= dx * this.lookSpeed;
13723
+ _euler.x = import_three35.MathUtils.clamp(_euler.x - dy * this.lookSpeed, -PITCH_LIMIT, PITCH_LIMIT);
13724
+ _euler.z = 0;
13725
+ this.object.quaternion.setFromEuler(_euler);
13726
+ this.notifyChange();
13727
+ }
13728
+ };
13729
+ function isFlyControls(obj) {
13730
+ return obj?.isFlyControls === true;
13731
+ }
13732
+
13733
+ // src/renderer/MineRenderScene.ts
13734
+ var import_three36 = require("three");
13272
13735
  init_SceneObject();
13273
13736
  var import_ts_deepmerge8 = __toESM(require_dist());
13274
13737
  init_ModelObject();
@@ -14178,7 +14641,7 @@ var SkinObject = class extends SceneObject {
14178
14641
  // src/renderer/MineRenderScene.ts
14179
14642
  init_EntityObject();
14180
14643
  init_ItemTints();
14181
- var _MineRenderScene = class _MineRenderScene extends import_three35.Scene {
14644
+ var _MineRenderScene = class _MineRenderScene extends import_three36.Scene {
14182
14645
  constructor(options) {
14183
14646
  super();
14184
14647
  this.isMineRenderScene = true;
@@ -14333,7 +14796,7 @@ function isMineRenderScene(obj) {
14333
14796
  }
14334
14797
 
14335
14798
  // src/renderer/Renderer.ts
14336
- var import_three37 = require("three");
14799
+ var import_three38 = require("three");
14337
14800
  var import_ts_deepmerge9 = __toESM(require_dist());
14338
14801
  var import_stats = __toESM(require("stats.js"));
14339
14802
  var import_postprocessing = require("postprocessing");
@@ -14342,11 +14805,11 @@ init_Model();
14342
14805
  init_three();
14343
14806
 
14344
14807
  // src/three/OrbitControls.js
14345
- var import_three36 = require("three");
14808
+ var import_three37 = require("three");
14346
14809
  var _changeEvent = { type: "change" };
14347
14810
  var _startEvent = { type: "start" };
14348
14811
  var _endEvent = { type: "end" };
14349
- var OrbitControls = class extends import_three36.EventDispatcher {
14812
+ var OrbitControls = class extends import_three37.EventDispatcher {
14350
14813
  constructor(object4, domElement) {
14351
14814
  super();
14352
14815
  if (domElement === void 0) console.warn('THREE.OrbitControls: The second parameter "domElement" is now mandatory.');
@@ -14355,7 +14818,7 @@ var OrbitControls = class extends import_three36.EventDispatcher {
14355
14818
  this.domElement = domElement;
14356
14819
  this.domElement.style.touchAction = "none";
14357
14820
  this.enabled = true;
14358
- this.target = new import_three36.Vector3();
14821
+ this.target = new import_three37.Vector3();
14359
14822
  this.minDistance = 0;
14360
14823
  this.maxDistance = Infinity;
14361
14824
  this.minZoom = 0;
@@ -14377,8 +14840,8 @@ var OrbitControls = class extends import_three36.EventDispatcher {
14377
14840
  this.autoRotate = false;
14378
14841
  this.autoRotateSpeed = 2;
14379
14842
  this.keys = { LEFT: "ArrowLeft", UP: "ArrowUp", RIGHT: "ArrowRight", BOTTOM: "ArrowDown" };
14380
- this.mouseButtons = { LEFT: import_three36.MOUSE.ROTATE, MIDDLE: import_three36.MOUSE.DOLLY, RIGHT: import_three36.MOUSE.PAN };
14381
- this.touches = { ONE: import_three36.TOUCH.ROTATE, TWO: import_three36.TOUCH.DOLLY_PAN };
14843
+ this.mouseButtons = { LEFT: import_three37.MOUSE.ROTATE, MIDDLE: import_three37.MOUSE.DOLLY, RIGHT: import_three37.MOUSE.PAN };
14844
+ this.touches = { ONE: import_three37.TOUCH.ROTATE, TWO: import_three37.TOUCH.DOLLY_PAN };
14382
14845
  this.target0 = this.target.clone();
14383
14846
  this.position0 = this.object.position.clone();
14384
14847
  this.zoom0 = this.object.zoom;
@@ -14407,11 +14870,11 @@ var OrbitControls = class extends import_three36.EventDispatcher {
14407
14870
  state = STATE.NONE;
14408
14871
  };
14409
14872
  this.update = function() {
14410
- const offset = new import_three36.Vector3();
14411
- const quat = new import_three36.Quaternion().setFromUnitVectors(object4.up, new import_three36.Vector3(0, 1, 0));
14873
+ const offset = new import_three37.Vector3();
14874
+ const quat = new import_three37.Quaternion().setFromUnitVectors(object4.up, new import_three37.Vector3(0, 1, 0));
14412
14875
  const quatInverse = quat.clone().invert();
14413
- const lastPosition = new import_three36.Vector3();
14414
- const lastQuaternion = new import_three36.Quaternion();
14876
+ const lastPosition = new import_three37.Vector3();
14877
+ const lastQuaternion = new import_three37.Quaternion();
14415
14878
  const twoPI = 2 * Math.PI;
14416
14879
  return function update() {
14417
14880
  const position = scope.object.position;
@@ -14497,20 +14960,20 @@ var OrbitControls = class extends import_three36.EventDispatcher {
14497
14960
  };
14498
14961
  let state = STATE.NONE;
14499
14962
  const EPS = 1e-6;
14500
- const spherical = new import_three36.Spherical();
14501
- const sphericalDelta = new import_three36.Spherical();
14963
+ const spherical = new import_three37.Spherical();
14964
+ const sphericalDelta = new import_three37.Spherical();
14502
14965
  let scale = 1;
14503
- const panOffset = new import_three36.Vector3();
14966
+ const panOffset = new import_three37.Vector3();
14504
14967
  let zoomChanged = false;
14505
- const rotateStart = new import_three36.Vector2();
14506
- const rotateEnd = new import_three36.Vector2();
14507
- const rotateDelta = new import_three36.Vector2();
14508
- const panStart = new import_three36.Vector2();
14509
- const panEnd = new import_three36.Vector2();
14510
- const panDelta = new import_three36.Vector2();
14511
- const dollyStart = new import_three36.Vector2();
14512
- const dollyEnd = new import_three36.Vector2();
14513
- const dollyDelta = new import_three36.Vector2();
14968
+ const rotateStart = new import_three37.Vector2();
14969
+ const rotateEnd = new import_three37.Vector2();
14970
+ const rotateDelta = new import_three37.Vector2();
14971
+ const panStart = new import_three37.Vector2();
14972
+ const panEnd = new import_three37.Vector2();
14973
+ const panDelta = new import_three37.Vector2();
14974
+ const dollyStart = new import_three37.Vector2();
14975
+ const dollyEnd = new import_three37.Vector2();
14976
+ const dollyDelta = new import_three37.Vector2();
14514
14977
  const pointers = [];
14515
14978
  const pointerPositions = {};
14516
14979
  function getAutoRotationAngle() {
@@ -14526,7 +14989,7 @@ var OrbitControls = class extends import_three36.EventDispatcher {
14526
14989
  sphericalDelta.phi -= angle;
14527
14990
  }
14528
14991
  const panLeft = function() {
14529
- const v = new import_three36.Vector3();
14992
+ const v = new import_three37.Vector3();
14530
14993
  return function panLeft2(distance, objectMatrix) {
14531
14994
  v.setFromMatrixColumn(objectMatrix, 0);
14532
14995
  v.multiplyScalar(-distance);
@@ -14534,7 +14997,7 @@ var OrbitControls = class extends import_three36.EventDispatcher {
14534
14997
  };
14535
14998
  }();
14536
14999
  const panUp = function() {
14537
- const v = new import_three36.Vector3();
15000
+ const v = new import_three37.Vector3();
14538
15001
  return function panUp2(distance, objectMatrix) {
14539
15002
  if (scope.screenSpacePanning === true) {
14540
15003
  v.setFromMatrixColumn(objectMatrix, 1);
@@ -14547,7 +15010,7 @@ var OrbitControls = class extends import_three36.EventDispatcher {
14547
15010
  };
14548
15011
  }();
14549
15012
  const pan = function() {
14550
- const offset = new import_three36.Vector3();
15013
+ const offset = new import_three37.Vector3();
14551
15014
  return function pan2(deltaX, deltaY) {
14552
15015
  const element = scope.domElement;
14553
15016
  if (scope.object.isPerspectiveCamera) {
@@ -14796,12 +15259,12 @@ var OrbitControls = class extends import_three36.EventDispatcher {
14796
15259
  mouseAction = -1;
14797
15260
  }
14798
15261
  switch (mouseAction) {
14799
- case import_three36.MOUSE.DOLLY:
15262
+ case import_three37.MOUSE.DOLLY:
14800
15263
  if (scope.enableZoom === false) return;
14801
15264
  handleMouseDownDolly(event);
14802
15265
  state = STATE.DOLLY;
14803
15266
  break;
14804
- case import_three36.MOUSE.ROTATE:
15267
+ case import_three37.MOUSE.ROTATE:
14805
15268
  if (event.ctrlKey || event.metaKey || event.shiftKey) {
14806
15269
  if (scope.enablePan === false) return;
14807
15270
  handleMouseDownPan(event);
@@ -14812,7 +15275,7 @@ var OrbitControls = class extends import_three36.EventDispatcher {
14812
15275
  state = STATE.ROTATE;
14813
15276
  }
14814
15277
  break;
14815
- case import_three36.MOUSE.PAN:
15278
+ case import_three37.MOUSE.PAN:
14816
15279
  if (event.ctrlKey || event.metaKey || event.shiftKey) {
14817
15280
  if (scope.enableRotate === false) return;
14818
15281
  handleMouseDownRotate(event);
@@ -14868,12 +15331,12 @@ var OrbitControls = class extends import_three36.EventDispatcher {
14868
15331
  switch (pointers.length) {
14869
15332
  case 1:
14870
15333
  switch (scope.touches.ONE) {
14871
- case import_three36.TOUCH.ROTATE:
15334
+ case import_three37.TOUCH.ROTATE:
14872
15335
  if (scope.enableRotate === false) return;
14873
15336
  handleTouchStartRotate();
14874
15337
  state = STATE.TOUCH_ROTATE;
14875
15338
  break;
14876
- case import_three36.TOUCH.PAN:
15339
+ case import_three37.TOUCH.PAN:
14877
15340
  if (scope.enablePan === false) return;
14878
15341
  handleTouchStartPan();
14879
15342
  state = STATE.TOUCH_PAN;
@@ -14884,12 +15347,12 @@ var OrbitControls = class extends import_three36.EventDispatcher {
14884
15347
  break;
14885
15348
  case 2:
14886
15349
  switch (scope.touches.TWO) {
14887
- case import_three36.TOUCH.DOLLY_PAN:
15350
+ case import_three37.TOUCH.DOLLY_PAN:
14888
15351
  if (scope.enableZoom === false && scope.enablePan === false) return;
14889
15352
  handleTouchStartDollyPan();
14890
15353
  state = STATE.TOUCH_DOLLY_PAN;
14891
15354
  break;
14892
- case import_three36.TOUCH.DOLLY_ROTATE:
15355
+ case import_three37.TOUCH.DOLLY_ROTATE:
14893
15356
  if (scope.enableZoom === false && scope.enableRotate === false) return;
14894
15357
  handleTouchStartDollyRotate();
14895
15358
  state = STATE.TOUCH_DOLLY_ROTATE;
@@ -14956,7 +15419,7 @@ var OrbitControls = class extends import_three36.EventDispatcher {
14956
15419
  function trackPointer(event) {
14957
15420
  let position = pointerPositions[event.pointerId];
14958
15421
  if (position === void 0) {
14959
- position = new import_three36.Vector2();
15422
+ position = new import_three37.Vector2();
14960
15423
  pointerPositions[event.pointerId] = position;
14961
15424
  }
14962
15425
  position.set(event.pageX, event.pageY);
@@ -14976,10 +15439,10 @@ var MapControls = class extends OrbitControls {
14976
15439
  constructor(object4, domElement) {
14977
15440
  super(object4, domElement);
14978
15441
  this.screenSpacePanning = false;
14979
- this.mouseButtons.LEFT = import_three36.MOUSE.PAN;
14980
- this.mouseButtons.RIGHT = import_three36.MOUSE.ROTATE;
14981
- this.touches.ONE = import_three36.TOUCH.PAN;
14982
- this.touches.TWO = import_three36.TOUCH.DOLLY_ROTATE;
15442
+ this.mouseButtons.LEFT = import_three37.MOUSE.PAN;
15443
+ this.mouseButtons.RIGHT = import_three37.MOUSE.ROTATE;
15444
+ this.touches.ONE = import_three37.TOUCH.PAN;
15445
+ this.touches.TWO = import_three37.TOUCH.DOLLY_ROTATE;
14983
15446
  }
14984
15447
  };
14985
15448
 
@@ -15050,14 +15513,14 @@ var _Renderer = class _Renderer {
15050
15513
  switch (this.options.camera.type) {
15051
15514
  case "perspective":
15052
15515
  default:
15053
- return new import_three37.PerspectiveCamera(
15516
+ return new import_three38.PerspectiveCamera(
15054
15517
  this.options.camera.perspective.fov,
15055
15518
  this.options.camera.perspective.aspect ?? this.viewWidth / this.viewHeight,
15056
15519
  this.options.camera.near,
15057
15520
  this.options.camera.far
15058
15521
  );
15059
15522
  case "orthographic":
15060
- return new import_three37.OrthographicCamera(
15523
+ return new import_three38.OrthographicCamera(
15061
15524
  this.options.camera.orthographic.left ?? this.viewWidth / -2,
15062
15525
  this.options.camera.orthographic.right ?? this.viewWidth / 2,
15063
15526
  this.options.camera.orthographic.top ?? this.viewHeight / 2,
@@ -15073,7 +15536,7 @@ var _Renderer = class _Renderer {
15073
15536
  }
15074
15537
  const pixelRatio = this.options.render.pixelRatio ?? 1;
15075
15538
  this._surface?.resize?.(Math.floor(this.viewWidth * pixelRatio), Math.floor(this.viewHeight * pixelRatio));
15076
- const renderer = new import_three37.WebGLRenderer({
15539
+ const renderer = new import_three38.WebGLRenderer({
15077
15540
  canvas: this._surface?.canvas,
15078
15541
  context: this._surface?.context,
15079
15542
  antialias: this.options.render.antialias,
@@ -15083,8 +15546,8 @@ var _Renderer = class _Renderer {
15083
15546
  });
15084
15547
  renderer.setClearColor(0, 0);
15085
15548
  renderer.shadowMap.enabled = true;
15086
- renderer.shadowMap.type = import_three37.PCFShadowMap;
15087
- renderer.outputColorSpace = import_three37.SRGBColorSpace;
15549
+ renderer.shadowMap.type = import_three38.PCFShadowMap;
15550
+ renderer.outputColorSpace = import_three38.SRGBColorSpace;
15088
15551
  if (this._surface) {
15089
15552
  renderer.setDrawingBufferSize(this.viewWidth, this.viewHeight, pixelRatio);
15090
15553
  } else {
@@ -15102,20 +15565,77 @@ var _Renderer = class _Renderer {
15102
15565
  }
15103
15566
  createControls() {
15104
15567
  if (!this.options.controls?.enabled) return void 0;
15105
- const position = this.camera.position.clone();
15106
- const controls = new OrbitControls(this.camera, this.renderer.domElement);
15107
- this.camera.position.copy(position);
15568
+ return this.createControlsFor(this.options.controls.mode ?? "orbit", this.initialTarget());
15569
+ }
15570
+ initialTarget() {
15108
15571
  const target = this.options.camera.lookingAt;
15109
- if (isVector3(target)) {
15572
+ if (isVector3(target)) return target.clone();
15573
+ if (isTripleArray(target)) return new import_three38.Vector3(target[0], target[1], target[2]);
15574
+ return new import_three38.Vector3();
15575
+ }
15576
+ createControlsFor(mode, target) {
15577
+ let controls;
15578
+ if (mode === "fly") {
15579
+ controls = new FlyControls(this.camera, this.renderer.domElement);
15580
+ controls.lookAt(target);
15581
+ controls.saveState();
15582
+ } else {
15583
+ const position = this.camera.position.clone();
15584
+ controls = new OrbitControls(this.camera, this.renderer.domElement);
15585
+ this.camera.position.copy(position);
15110
15586
  controls.target.copy(target);
15111
- } else if (isTripleArray(target)) {
15112
- controls.target.set(target[0], target[1], target[2]);
15587
+ controls.update();
15588
+ controls.saveState();
15113
15589
  }
15114
- controls.update();
15115
- controls.saveState();
15590
+ this._controlsTime = void 0;
15116
15591
  this.registerEventDispatcher(controls);
15117
15592
  return controls;
15118
15593
  }
15594
+ /**
15595
+ * Replaces renderer-owned controls with the selected mode, keeping the camera where it is.
15596
+ * Switching to orbit controls targets the point the camera looks at, at the previous orbit distance.
15597
+ * Returns the new controls, or undefined when controls were disabled at construction or after disposal.
15598
+ */
15599
+ setControlsMode(mode) {
15600
+ const current = this._controls;
15601
+ if (this._disposed || !current) return void 0;
15602
+ if (this.controlsMode === mode) return current;
15603
+ let target;
15604
+ if (isFlyControls(current)) {
15605
+ const distance = this._orbitDistance ?? (this.camera.position.distanceTo(this.initialTarget()) || 1);
15606
+ target = new import_three38.Vector3(0, 0, -distance).applyQuaternion(this.camera.quaternion).add(this.camera.position);
15607
+ } else {
15608
+ target = current.target.clone();
15609
+ this._orbitDistance = this.camera.position.distanceTo(target);
15610
+ }
15611
+ const enabled = current.enabled;
15612
+ this.disposeControls(current);
15613
+ this._controls = this.createControlsFor(mode, target);
15614
+ this._controls.enabled = enabled;
15615
+ this.dirty = true;
15616
+ return this._controls;
15617
+ }
15618
+ disposeControls(controls) {
15619
+ this._controls = void 0;
15620
+ this.unregisterEventDispatcher(controls);
15621
+ controls.enabled = false;
15622
+ controls.dispose();
15623
+ }
15624
+ /** Advances owned controls; without a frame time, flight movement does not progress. */
15625
+ updateControls(time) {
15626
+ const controls = this._controls;
15627
+ if (!controls?.enabled) {
15628
+ this._controlsTime = void 0;
15629
+ return;
15630
+ }
15631
+ if (!isFlyControls(controls)) {
15632
+ controls.update();
15633
+ return;
15634
+ }
15635
+ const previous = this._controlsTime;
15636
+ if (time !== void 0) this._controlsTime = time;
15637
+ controls.update(time === void 0 || previous === void 0 ? 0 : (time - previous) / 1e3);
15638
+ }
15119
15639
  //</editor-fold>
15120
15640
  init() {
15121
15641
  if (this._disposed) return;
@@ -15123,20 +15643,20 @@ var _Renderer = class _Renderer {
15123
15643
  window["__THREE_DEVTOOLS__"].dispatchEvent(new CustomEvent("observe", { detail: this.scene }));
15124
15644
  }
15125
15645
  if (this.options.debug.grid) {
15126
- const gridHelper = new import_three37.GridHelper(128, 16);
15646
+ const gridHelper = new import_three38.GridHelper(128, 16);
15127
15647
  this._debugHelpers.push(gridHelper);
15128
15648
  this.scene.add(gridHelper);
15129
- const gridHelper2 = new import_three37.GridHelper(128, 16);
15649
+ const gridHelper2 = new import_three38.GridHelper(128, 16);
15130
15650
  this._debugHelpers.push(gridHelper2);
15131
15651
  gridHelper2.rotation.x = 90 * (Math.PI / 180);
15132
15652
  this.scene.add(gridHelper2);
15133
- const gridHelper3 = new import_three37.GridHelper(128, 16);
15653
+ const gridHelper3 = new import_three38.GridHelper(128, 16);
15134
15654
  this._debugHelpers.push(gridHelper3);
15135
15655
  gridHelper3.rotation.z = 90 * (Math.PI / 180);
15136
15656
  this.scene.add(gridHelper3);
15137
15657
  }
15138
15658
  if (this.options.debug.axes) {
15139
- const axesHelper = new import_three37.AxesHelper(64);
15659
+ const axesHelper = new import_three38.AxesHelper(64);
15140
15660
  this._debugHelpers.push(axesHelper);
15141
15661
  this.scene.add(axesHelper);
15142
15662
  }
@@ -15192,6 +15712,12 @@ var _Renderer = class _Renderer {
15192
15712
  events.add(changeEvent2);
15193
15713
  dispatcher.addEventListener(changeEvent2, this._changeListener);
15194
15714
  }
15715
+ unregisterEventDispatcher(dispatcher) {
15716
+ const events = this._eventDispatchers.get(dispatcher);
15717
+ if (!events) return;
15718
+ for (const event of events) dispatcher.removeEventListener(event, this._changeListener);
15719
+ this._eventDispatchers.delete(dispatcher);
15720
+ }
15195
15721
  /** Updates the camera and canvas size. Width and height are in CSS pixels. */
15196
15722
  resize(width, height) {
15197
15723
  if (this._disposed) return;
@@ -15268,6 +15794,7 @@ var _Renderer = class _Renderer {
15268
15794
  this.renderer.setAnimationLoop(null);
15269
15795
  }
15270
15796
  this._nextFrameTime = void 0;
15797
+ this._controlsTime = void 0;
15271
15798
  for (const subscription of this._frameCallbacks.values()) subscription.previous = void 0;
15272
15799
  }
15273
15800
  /**
@@ -15297,10 +15824,7 @@ var _Renderer = class _Renderer {
15297
15824
  }
15298
15825
  this._element = void 0;
15299
15826
  if (this._controls) {
15300
- const controls = this._controls;
15301
- this._controls = void 0;
15302
- controls.enabled = false;
15303
- controls.dispose();
15827
+ this.disposeControls(this._controls);
15304
15828
  }
15305
15829
  this.scene.clear();
15306
15830
  for (const helper of this._debugHelpers) {
@@ -15317,9 +15841,7 @@ var _Renderer = class _Renderer {
15317
15841
  }
15318
15842
  animate(t = performance.now()) {
15319
15843
  if (!this._running) return;
15320
- if (this._controls?.enabled) {
15321
- this._controls.update();
15322
- }
15844
+ this.updateControls(t);
15323
15845
  if (!this._running) return;
15324
15846
  if (!this.dirty && !this.options.render.renderAlways && !this._frameCallbacks.size && !this._videoExporter) return;
15325
15847
  const interval = this._frameInterval;
@@ -15356,7 +15878,7 @@ var _Renderer = class _Renderer {
15356
15878
  /** Draws a fresh frame without starting the animation loop or invoking frame callbacks. */
15357
15879
  renderOnce() {
15358
15880
  if (this._disposed) throw new Error("Cannot render a disposed renderer");
15359
- if (this._controls?.enabled) this._controls.update();
15881
+ this.updateControls();
15360
15882
  this.drawFrame();
15361
15883
  }
15362
15884
  /**
@@ -15386,7 +15908,7 @@ var _Renderer = class _Renderer {
15386
15908
  try {
15387
15909
  if (!wasRunning) this.start();
15388
15910
  this._videoExporter = exporter;
15389
- if (this._controls?.enabled) this._controls.update();
15911
+ this.updateControls();
15390
15912
  this.drawFrame();
15391
15913
  } catch (error) {
15392
15914
  exporter.cancel(error);
@@ -15437,6 +15959,19 @@ var _Renderer = class _Renderer {
15437
15959
  get controls() {
15438
15960
  return this._controls;
15439
15961
  }
15962
+ /** Renderer-owned OrbitControls, or undefined in fly mode or without controls. */
15963
+ get orbitControls() {
15964
+ return this._controls && !isFlyControls(this._controls) ? this._controls : void 0;
15965
+ }
15966
+ /** Renderer-owned {@link FlyControls}, or undefined in orbit mode or without controls. */
15967
+ get flyControls() {
15968
+ return isFlyControls(this._controls) ? this._controls : void 0;
15969
+ }
15970
+ /** The active renderer-owned controls mode, or undefined without controls. */
15971
+ get controlsMode() {
15972
+ if (!this._controls) return void 0;
15973
+ return isFlyControls(this._controls) ? "fly" : "orbit";
15974
+ }
15440
15975
  ///
15441
15976
  };
15442
15977
  _Renderer.DEFAULT_OPTIONS = (0, import_ts_deepmerge9.default)({}, {
@@ -15454,8 +15989,8 @@ _Renderer.DEFAULT_OPTIONS = (0, import_ts_deepmerge9.default)({}, {
15454
15989
  top: void 0,
15455
15990
  bottom: void 0
15456
15991
  },
15457
- position: new import_three37.Vector3(50, 50, 50),
15458
- lookingAt: new import_three37.Vector3(0, 0, 0)
15992
+ position: new import_three38.Vector3(50, 50, 50),
15993
+ lookingAt: new import_three38.Vector3(0, 0, 0)
15459
15994
  },
15460
15995
  render: {
15461
15996
  fpsLimit: 60,
@@ -15470,7 +16005,8 @@ _Renderer.DEFAULT_OPTIONS = (0, import_ts_deepmerge9.default)({}, {
15470
16005
  enabled: true
15471
16006
  },
15472
16007
  controls: {
15473
- enabled: false
16008
+ enabled: false,
16009
+ mode: "orbit"
15474
16010
  },
15475
16011
  debug: {
15476
16012
  grid: false,
@@ -15724,7 +16260,7 @@ function parseSceneDocument(value) {
15724
16260
  }
15725
16261
 
15726
16262
  // src/scene/SceneDocumentLoader.ts
15727
- var import_three39 = require("three");
16263
+ var import_three40 = require("three");
15728
16264
  init_AssetKey();
15729
16265
  init_AssetLoader();
15730
16266
  init_BlockStates();
@@ -15785,7 +16321,7 @@ var SceneDocumentLoader = class {
15785
16321
  if (definition.minecraftVersion !== void 0 && definition.minecraftVersion !== AssetLoader.version) {
15786
16322
  throw new Error(`Scene requires Minecraft ${definition.minecraftVersion}; call AssetLoader.setVersion("${definition.minecraftVersion}") before loading it`);
15787
16323
  }
15788
- const root = new import_three39.Group();
16324
+ const root = new import_three40.Group();
15789
16325
  root.name = "MineRender scene document";
15790
16326
  const objects = [];
15791
16327
  let disposed = false;
@@ -15825,7 +16361,7 @@ var SceneDocumentLoader = class {
15825
16361
  return this.loadValidatedObject(scene, definition, parent);
15826
16362
  }
15827
16363
  static async loadValidatedObject(scene, definition, parent) {
15828
- const root = new import_three39.Group();
16364
+ const root = new import_three40.Group();
15829
16365
  root.name = definition.name ?? definition.id;
15830
16366
  root.userData.sceneObjectId = definition.id;
15831
16367
  root.position.fromArray(definition.position ?? [0, 0, 0]);
@@ -16703,12 +17239,12 @@ var AnvilWorldSource = class {
16703
17239
  };
16704
17240
 
16705
17241
  // src/world/Chunk.ts
16706
- var import_three43 = require("three");
17242
+ var import_three44 = require("three");
16707
17243
  init_BlockStates();
16708
17244
  init_AssetKey();
16709
17245
 
16710
17246
  // src/world/MineRenderWorld.ts
16711
- var import_three41 = require("three");
17247
+ var import_three42 = require("three");
16712
17248
  init_Model();
16713
17249
  init_util();
16714
17250
  init_AssetKey();
@@ -16716,7 +17252,7 @@ init_BlockStates();
16716
17252
  init_CubeFace();
16717
17253
 
16718
17254
  // src/world/SectionModels.ts
16719
- var import_three40 = require("three");
17255
+ var import_three41 = require("three");
16720
17256
  init_AssetKey();
16721
17257
  init_Models();
16722
17258
  init_CubeFace();
@@ -16740,7 +17276,7 @@ function cached(map, key, load) {
16740
17276
  }
16741
17277
  function compactQuads(geometry, keep) {
16742
17278
  const quads = keep.flatMap((kept, index) => kept ? [index] : []);
16743
- const compact = new import_three40.BufferGeometry();
17279
+ const compact = new import_three41.BufferGeometry();
16744
17280
  for (const [name, size] of Object.entries({ position: 3, normal: 3, uv: 2, uvBounds: 4, color: 3 })) {
16745
17281
  const source2 = geometry.getAttribute(name);
16746
17282
  const defaults = name === "uvBounds" ? [0, 0, 1, 1] : name === "color" ? [1, 1, 1] : void 0;
@@ -16752,7 +17288,7 @@ function compactQuads(geometry, keep) {
16752
17288
  }
16753
17289
  }
16754
17290
  }
16755
- compact.setAttribute(name, new import_three40.BufferAttribute(values, size));
17291
+ compact.setAttribute(name, new import_three41.BufferAttribute(values, size));
16756
17292
  }
16757
17293
  const source = geometry.getIndex();
16758
17294
  const indices = [];
@@ -16773,24 +17309,48 @@ var SectionModels = class {
16773
17309
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("maxAtlasSize must be a positive integer");
16774
17310
  }
16775
17311
  /** Returns templates for the matched parts, or `undefined` when the block needs an individual render object. */
16776
- async get(blockState3, properties = {}, position) {
16777
- const variantPosition = position ? [...position] : void 0;
16778
- return (await this.prepareState(blockState3, properties))?.pick(variantPosition);
17312
+ async get(blockState3, properties = {}, position, biome) {
17313
+ const biomePosition = position ? [...position] : void 0;
17314
+ const prepared = await this.state(blockState3, properties);
17315
+ if (!prepared) return void 0;
17316
+ return Promise.all(prepared.groups.map(async (group) => {
17317
+ const variant = BlockStateResolver.choose(group, biomePosition);
17318
+ const choice = prepared.choices.get(variant);
17319
+ const tints = await BlockTints.get(
17320
+ blockState3.key,
17321
+ prepared.state,
17322
+ choice.model,
17323
+ void 0,
17324
+ () => BlockTints.getBiomeColor(blockState3.key, biome, biomePosition)
17325
+ );
17326
+ return this.template(variant, choice, tints);
17327
+ }));
16779
17328
  }
16780
- /** Resolves section templates once, with a synchronous weighted choice for each cell. */
16781
- async prepareState(blockState3, properties = {}) {
17329
+ /** Resolves section templates for one tint palette, with a synchronous weighted choice for each cell. */
17330
+ async prepareState(blockState3, properties = {}, biomeColor) {
17331
+ const prepared = await this.state(blockState3, properties);
17332
+ if (!prepared) return void 0;
17333
+ let color2;
17334
+ const resolveColor = typeof biomeColor === "function" ? () => color2 ?? (color2 = biomeColor()) : biomeColor;
17335
+ const templates = /* @__PURE__ */ new Map();
17336
+ await Promise.all([...prepared.choices].map(async ([variant, choice]) => {
17337
+ const tints = await BlockTints.get(blockState3.key, prepared.state, choice.model, void 0, resolveColor);
17338
+ templates.set(variant, await this.template(variant, choice, tints));
17339
+ }));
17340
+ return { pick: (position) => prepared.groups.map((group) => templates.get(BlockStateResolver.choose(group, position))) };
17341
+ }
17342
+ async state(blockState3, properties) {
16782
17343
  let states = this.states.get(blockState3);
16783
17344
  if (!states) this.states.set(blockState3, states = /* @__PURE__ */ new Map());
16784
17345
  const key = JSON.stringify(Object.entries(properties).sort(([a], [b]) => a.localeCompare(b)));
16785
- const prepared = await cached(states, key, () => this.prepare(blockState3, properties));
16786
- if (!prepared) return void 0;
16787
- return { pick: (position) => prepared.groups.map((group) => prepared.templates.get(BlockStateResolver.choose(group, position))) };
17346
+ return cached(states, key, () => this.prepare(blockState3, properties));
16788
17347
  }
16789
17348
  /** Returns the shared still/flow atlas used by per-block fluid objects. */
16790
17349
  fluidAtlas(kind, root) {
16791
- return cached(this.fluidAtlases, kind, async () => {
17350
+ const key = new AssetKey("minecraft", kind, "models", "fluid", "assets", ".json", root);
17351
+ return cached(this.fluidAtlases, key.serialize(), async () => {
16792
17352
  const atlas = await UVMapper.getAtlas({
16793
- key: new AssetKey("minecraft", kind, "models", "fluid", "assets", ".json", root),
17353
+ key,
16794
17354
  textures: { still: `minecraft:block/${kind}_still`, flow: `minecraft:block/${kind}_flow` },
16795
17355
  elements: []
16796
17356
  });
@@ -16802,64 +17362,67 @@ var SectionModels = class {
16802
17362
  const state = { ...await BlockStateResolver.defaults(blockState3), ...properties };
16803
17363
  const groups = BlockStateResolver.matching(blockState3, state);
16804
17364
  if (!groups.length) return void 0;
16805
- const templates = /* @__PURE__ */ new Map();
17365
+ const prepared = /* @__PURE__ */ new Map();
16806
17366
  for (const group of groups) {
16807
17367
  const choices = Array.isArray(group) ? group : [group];
16808
17368
  if (!choices.length) return void 0;
16809
17369
  for (const variant of choices) {
16810
17370
  if (!variant.model) return void 0;
16811
- const model = await Models.getMerged(AssetKey.parse("models", variant.model));
17371
+ const modelKey = AssetKey.parse("models", variant.model);
17372
+ modelKey.root = blockState3.key?.root;
17373
+ const model = await Models.getMerged(modelKey);
16812
17374
  if (!model?.elements?.length) return void 0;
16813
17375
  const atlas = await UVMapper.getAtlas(model);
16814
17376
  const rotation = BlockStateResolver.rotation(variant);
16815
17377
  if (!atlas || atlas.image.width > this.maxAtlasSize || atlas.image.height > this.maxAtlasSize) return void 0;
16816
- const tints = await BlockTints.get(blockState3.key, state, model);
16817
- let models = this.models.get(model);
16818
- if (!models) this.models.set(model, models = /* @__PURE__ */ new Map());
16819
- const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints]);
16820
- const template = await cached(models, key, async () => {
16821
- const object4 = new SectionModel(model, {
16822
- instanceMeshes: false,
16823
- mergeMeshes: true,
16824
- tints,
16825
- uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0) ? [rotation.x, rotation.y, rotation.z] : void 0
16826
- });
16827
- await object4.init();
16828
- let geometry = object4.children[0].geometry;
16829
- geometry.applyMatrix4(new import_three40.Matrix4().makeRotationFromEuler(rotation));
16830
- object4.clear();
16831
- const keep = [];
16832
- const cullFaces = [];
16833
- for (const element of atlas.model.elements) {
16834
- for (const face of CUBE_FACES) {
16835
- const data = element.faces[face];
16836
- keep.push(data !== void 0);
16837
- if (!data) continue;
16838
- const local = CUBE_FACES.findIndex((name) => name === data.cullface);
16839
- if (local < 0) {
16840
- cullFaces.push(0);
16841
- continue;
16842
- }
16843
- const normal = new import_three40.Vector3(...CUBE_FACE_OFFSETS[local]).applyEuler(rotation);
16844
- const world = CUBE_FACE_OFFSETS.findIndex(([x, y, z]) => Math.abs(normal.x - x) < 1e-6 && Math.abs(normal.y - y) < 1e-6 && Math.abs(normal.z - z) < 1e-6);
16845
- cullFaces.push(world < 0 ? 0 : 1 << world);
16846
- }
16847
- }
16848
- geometry = compactQuads(geometry, keep);
16849
- const template2 = {
16850
- geometry,
16851
- atlas,
16852
- cullFaces: new Uint8Array(cullFaces),
16853
- occludes: ModelCulling.isOpaqueFullCube(atlas) && ModelCulling.toLocalMask(63, rotation) === 63,
16854
- layer: atlas.hasTranslucency ? 1 : 0
16855
- };
16856
- this.templates.add(template2);
16857
- return template2;
16858
- });
16859
- templates.set(variant, template);
17378
+ prepared.set(variant, { model, atlas, rotation });
16860
17379
  }
16861
17380
  }
16862
- return { groups, templates };
17381
+ return { state, groups, choices: prepared };
17382
+ }
17383
+ async template(variant, { model, atlas, rotation }, tints) {
17384
+ let models = this.models.get(model);
17385
+ if (!models) this.models.set(model, models = /* @__PURE__ */ new Map());
17386
+ const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints]);
17387
+ return cached(models, key, async () => {
17388
+ const object4 = new SectionModel(model, {
17389
+ instanceMeshes: false,
17390
+ mergeMeshes: true,
17391
+ tints,
17392
+ uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0) ? [rotation.x, rotation.y, rotation.z] : void 0
17393
+ });
17394
+ await object4.init();
17395
+ let geometry = object4.children[0].geometry;
17396
+ geometry.applyMatrix4(new import_three41.Matrix4().makeRotationFromEuler(rotation));
17397
+ object4.clear();
17398
+ const keep = [];
17399
+ const cullFaces = [];
17400
+ for (const element of atlas.model.elements) {
17401
+ for (const face of CUBE_FACES) {
17402
+ const data = element.faces[face];
17403
+ keep.push(data !== void 0);
17404
+ if (!data) continue;
17405
+ const local = CUBE_FACES.findIndex((name) => name === data.cullface);
17406
+ if (local < 0) {
17407
+ cullFaces.push(0);
17408
+ continue;
17409
+ }
17410
+ const normal = new import_three41.Vector3(...CUBE_FACE_OFFSETS[local]).applyEuler(rotation);
17411
+ const world = CUBE_FACE_OFFSETS.findIndex(([x, y, z]) => Math.abs(normal.x - x) < 1e-6 && Math.abs(normal.y - y) < 1e-6 && Math.abs(normal.z - z) < 1e-6);
17412
+ cullFaces.push(world < 0 ? 0 : 1 << world);
17413
+ }
17414
+ }
17415
+ geometry = compactQuads(geometry, keep);
17416
+ const template = {
17417
+ geometry,
17418
+ atlas,
17419
+ cullFaces: new Uint8Array(cullFaces),
17420
+ occludes: ModelCulling.isOpaqueFullCube(atlas) && ModelCulling.toLocalMask(63, rotation) === 63,
17421
+ layer: atlas.hasTranslucency ? 1 : 0
17422
+ };
17423
+ this.templates.add(template);
17424
+ return template;
17425
+ });
16863
17426
  }
16864
17427
  /** Disposes prepared geometry and clears this world's template caches. */
16865
17428
  clear() {
@@ -17036,17 +17599,17 @@ var MineRenderWorld = class {
17036
17599
  }
17037
17600
  getBlockAt(posOrX, y, z) {
17038
17601
  if (typeof posOrX == "number") {
17039
- return this.getBlockAt(new import_three41.Vector3(posOrX, y, z));
17602
+ return this.getBlockAt(new import_three42.Vector3(posOrX, y, z));
17040
17603
  }
17041
17604
  if (isTripleArray(posOrX)) {
17042
- return this.getBlockAt(new import_three41.Vector3(posOrX[0], posOrX[1], posOrX[2]));
17605
+ return this.getBlockAt(new import_three42.Vector3(posOrX[0], posOrX[1], posOrX[2]));
17043
17606
  }
17044
17607
  this.validatePosBounds(posOrX);
17045
17608
  return this.getChunkAt(posOrX)?.getBlockAt(posOrX);
17046
17609
  }
17047
17610
  getBiomeAt(posOrX, y, z) {
17048
- if (typeof posOrX === "number") return this.getBiomeAt(new import_three41.Vector3(posOrX, y, z));
17049
- if (isTripleArray(posOrX)) return this.getBiomeAt(new import_three41.Vector3(...posOrX));
17611
+ if (typeof posOrX === "number") return this.getBiomeAt(new import_three42.Vector3(posOrX, y, z));
17612
+ if (isTripleArray(posOrX)) return this.getBiomeAt(new import_three42.Vector3(...posOrX));
17050
17613
  this.validatePosBounds(posOrX);
17051
17614
  const cx = Math.floor(posOrX.x / 16), cy = Math.floor(posOrX.y / 16), cz = Math.floor(posOrX.z / 16);
17052
17615
  const lx = posOrX.x - cx * 16, ly = posOrX.y - cy * 16, lz = posOrX.z - cz * 16;
@@ -17054,10 +17617,10 @@ var MineRenderWorld = class {
17054
17617
  }
17055
17618
  async setBlockAt(posOrX, yOrBlock, z, block) {
17056
17619
  if (typeof posOrX == "number") {
17057
- return this.setBlockAt(new import_three41.Vector3(posOrX, yOrBlock, z), block);
17620
+ return this.setBlockAt(new import_three42.Vector3(posOrX, yOrBlock, z), block);
17058
17621
  }
17059
17622
  if (isTripleArray(posOrX)) {
17060
- return this.setBlockAt(new import_three41.Vector3(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
17623
+ return this.setBlockAt(new import_three42.Vector3(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
17061
17624
  }
17062
17625
  return this.placeBlock(posOrX, yOrBlock);
17063
17626
  }
@@ -17068,10 +17631,10 @@ var MineRenderWorld = class {
17068
17631
  }
17069
17632
  async setBlockVisibleAt(posOrX, yOrVisible, z, visible) {
17070
17633
  if (typeof posOrX === "number") {
17071
- return this.setBlockVisibleAt(new import_three41.Vector3(posOrX, yOrVisible, z), visible);
17634
+ return this.setBlockVisibleAt(new import_three42.Vector3(posOrX, yOrVisible, z), visible);
17072
17635
  }
17073
17636
  if (isTripleArray(posOrX)) {
17074
- return this.setBlockVisibleAt(new import_three41.Vector3(...posOrX), yOrVisible);
17637
+ return this.setBlockVisibleAt(new import_three42.Vector3(...posOrX), yOrVisible);
17075
17638
  }
17076
17639
  this.validatePosBounds(posOrX);
17077
17640
  await this.getChunkAt(posOrX)?.setBlockVisibleAt(posOrX, yOrVisible);
@@ -17097,7 +17660,7 @@ var MineRenderWorld = class {
17097
17660
  const collect = async (positions2) => {
17098
17661
  for (const pos of positions2) changes.set(pos.toArray().join(","), pos);
17099
17662
  };
17100
- const place = (block) => this.placeBlock(new import_three41.Vector3(...block.position), block, collect);
17663
+ const place = (block) => this.placeBlock(new import_three42.Vector3(...block.position), block, collect);
17101
17664
  const keys = /* @__PURE__ */ new Map();
17102
17665
  const types = /* @__PURE__ */ new Set();
17103
17666
  for (const block of multiblock.blocks) {
@@ -17113,7 +17676,7 @@ var MineRenderWorld = class {
17113
17676
  }
17114
17677
  if (!executor) {
17115
17678
  const groups2 = /* @__PURE__ */ new Map();
17116
- const position = new import_three41.Vector3();
17679
+ const position = new import_three42.Vector3();
17117
17680
  for (const block of multiblock.blocks) {
17118
17681
  const [x, y, z] = block.position;
17119
17682
  this.validatePosBounds(position.set(x, y, z));
@@ -17153,7 +17716,7 @@ var MineRenderWorld = class {
17153
17716
  try {
17154
17717
  positions = await chunk.placeBlocks(blocks);
17155
17718
  } finally {
17156
- positions ?? (positions = blocks.map(({ index }) => new import_three41.Vector3(
17719
+ positions ?? (positions = blocks.map(({ index }) => new import_three42.Vector3(
17157
17720
  chunk.x * 16 + index % 16,
17158
17721
  chunk.y * 16 + Math.floor(index / 256),
17159
17722
  chunk.z * 16 + Math.floor(index / 16) % 16
@@ -17189,10 +17752,9 @@ var MineRenderWorld = class {
17189
17752
  }
17190
17753
  try {
17191
17754
  await this.clearChunkColumn(chunk.x, chunk.z);
17192
- let sliceStart = performance.now();
17193
17755
  for (const section of chunk.sections) {
17194
17756
  if (section.biomes) {
17195
- const position = new import_three41.Vector3(chunk.x * 16, section.y * 16, chunk.z * 16);
17757
+ const position = new import_three42.Vector3(chunk.x * 16, section.y * 16, chunk.z * 16);
17196
17758
  this.validatePosBounds(position);
17197
17759
  const copy = [...section.biomes];
17198
17760
  if (copy.length !== 64 || !copy.every((id) => typeof id === "string" && id.length > 0)) {
@@ -17203,9 +17765,12 @@ var MineRenderWorld = class {
17203
17765
  if (!column) this.biomes.set(key, column = /* @__PURE__ */ new Map());
17204
17766
  column.set(section.y, copy);
17205
17767
  }
17768
+ }
17769
+ let sliceStart = performance.now();
17770
+ for (const section of chunk.sections) {
17206
17771
  if (!executor) {
17207
17772
  if (!occupiedSections.has(section.y)) continue;
17208
- const position = new import_three41.Vector3(chunk.x * 16, section.y * 16, chunk.z * 16);
17773
+ const position = new import_three42.Vector3(chunk.x * 16, section.y * 16, chunk.z * 16);
17209
17774
  this.validatePosBounds(position);
17210
17775
  const target = this.getOrCreateChunkAt(position);
17211
17776
  try {
@@ -17299,7 +17864,7 @@ var MineRenderWorld = class {
17299
17864
  this.sectionModels?.clear();
17300
17865
  }
17301
17866
  updateCulling(positions) {
17302
- const neighbor = new import_three41.Vector3();
17867
+ const neighbor = new import_three42.Vector3();
17303
17868
  for (const pos of positions) {
17304
17869
  const cx = Math.floor(pos.x / 16), cy = Math.floor(pos.y / 16), cz = Math.floor(pos.z / 16);
17305
17870
  const chunk = this._chunks.get(`${cx}_${cy}_${cz}`);
@@ -17367,18 +17932,34 @@ var MineRenderWorld = class {
17367
17932
  const key = this.worldPosToChunkKey(pos);
17368
17933
  let chunk = this._chunks.get(key);
17369
17934
  if (typeof chunk === "undefined") {
17935
+ const x = Math.floor(pos.x / 16), y = Math.floor(pos.y / 16), z = Math.floor(pos.z / 16);
17370
17936
  chunk = new Chunk(
17371
17937
  this.scene,
17372
- Math.floor(pos.x / 16),
17373
- Math.floor(pos.y / 16),
17374
- Math.floor(pos.z / 16),
17938
+ x,
17939
+ y,
17940
+ z,
17375
17941
  (positions) => this.updateCulling(positions),
17376
17942
  this.sectionModels,
17377
- (x, y, z) => {
17378
- const cx = Math.floor(x / 16), cy = Math.floor(y / 16), cz = Math.floor(z / 16);
17943
+ (x2, y2, z2) => {
17944
+ const cx = Math.floor(x2 / 16), cy = Math.floor(y2 / 16), cz = Math.floor(z2 / 16);
17379
17945
  const section = this._chunks.get(`${cx}_${cy}_${cz}`);
17380
- const index = (y - cy * 16) * 256 + (z - cz * 16) * 16 + x - cx * 16;
17946
+ const index = (y2 - cy * 16) * 256 + (z2 - cz * 16) * 16 + x2 - cx * 16;
17381
17947
  return (section?.fluidByteIndex(index) ?? 0) | (section?.isOccludingIndex(index) ? 64 : 0);
17948
+ },
17949
+ (x2, y2, z2) => this.getBiomeAt(x2, y2, z2),
17950
+ async (biomes) => {
17951
+ if (this._chunks.get(key) !== chunk) return;
17952
+ const columnKey = `${x}_${z}`;
17953
+ let column = this.biomes.get(columnKey);
17954
+ if (biomes) {
17955
+ if (!column) this.biomes.set(columnKey, column = /* @__PURE__ */ new Map());
17956
+ column.set(y, biomes);
17957
+ } else {
17958
+ column?.delete(y);
17959
+ if (!column?.size) this.biomes.delete(columnKey);
17960
+ }
17961
+ await chunk.refreshBiomes();
17962
+ await this._chunks.get(`${x}_${y + 1}_${z}`)?.refreshBiomes();
17382
17963
  }
17383
17964
  );
17384
17965
  this._chunks.set(key, chunk);
@@ -17391,7 +17972,7 @@ var MineRenderWorld = class {
17391
17972
  }
17392
17973
  /** Converts block coordinates to scene units by multiplying each component by 16. */
17393
17974
  static worldToScenePosition(pos) {
17394
- return new import_three41.Vector3(
17975
+ return new import_three42.Vector3(
17395
17976
  pos.x * 16,
17396
17977
  pos.y * 16,
17397
17978
  pos.z * 16
@@ -17399,7 +17980,7 @@ var MineRenderWorld = class {
17399
17980
  }
17400
17981
  /** Converts scene units to block coordinates without rounding to an integer block. */
17401
17982
  static sceneToWorldPosition(pos) {
17402
- return new import_three41.Vector3(
17983
+ return new import_three42.Vector3(
17403
17984
  pos.x / 16,
17404
17985
  pos.y / 16,
17405
17986
  pos.z / 16
@@ -17422,7 +18003,7 @@ var MineRenderWorld = class {
17422
18003
  init_Model();
17423
18004
 
17424
18005
  // src/world/SectionMesh.ts
17425
- var import_three42 = require("three");
18006
+ var import_three43 = require("three");
17426
18007
  init_CanvasCompat();
17427
18008
  init_Materials();
17428
18009
 
@@ -17433,6 +18014,7 @@ function buildSectionGeometry(input) {
17433
18014
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
17434
18015
  if (count > indices.length || count > templates.length || count > cullMasks.length) throw new RangeError("Section entry count exceeds its arrays");
17435
18016
  if (fluids && fluids.cells.length !== 5832) throw new RangeError("Section fluid cells must cover an 18\xD718\xD718 neighborhood");
18017
+ if (fluids?.waterTints && fluids.waterTints.length !== 4096 * 3) throw new RangeError("Section water tints must contain RGB for 4096 cells");
17436
18018
  for (const template of templateData) {
17437
18019
  const { quads } = template;
17438
18020
  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) {
@@ -17510,7 +18092,9 @@ function buildSectionGeometry(input) {
17510
18092
  height
17511
18093
  });
17512
18094
  }
17513
- fluidPages.get(fluid).fluids.push({ quads, x, y, z, data });
18095
+ const tintIndex = (x + z * 16 + y * 256) * 3;
18096
+ const tint = fluid === "water" && fluids.waterTints ? fluids.waterTints.subarray(tintIndex, tintIndex + 3) : data.tint;
18097
+ fluidPages.get(fluid).fluids.push({ quads, x, y, z, data, tint });
17514
18098
  }
17515
18099
  pages.push(...[...fluidPages.values()].sort((a, b) => Number(atlases[a.placements[0].atlas].animated) - Number(atlases[b.placements[0].atlas].animated) || a.placements[0].atlas - b.placements[0].atlas));
17516
18100
  }
@@ -17559,7 +18143,7 @@ function buildSectionGeometry(input) {
17559
18143
  for (let corner = 0; corner < 6; corner++) outputIndices[indexOffset++] = start + template.indices[quad * 6 + corner];
17560
18144
  }
17561
18145
  }
17562
- for (const { quads, x, y, z, data } of page.fluids) {
18146
+ for (const { quads, x, y, z, data, tint } of page.fluids) {
17563
18147
  const atlas = atlases[data.atlas];
17564
18148
  const placement = page.placements[0];
17565
18149
  const mapU = (u) => (placement.x + u * atlas.width) / page.width;
@@ -17583,7 +18167,7 @@ function buildSectionGeometry(input) {
17583
18167
  mapU((rect[0] + rect[2]) / atlas.width),
17584
18168
  mapV(1 - rect[1] / atlas.height)
17585
18169
  ], vertex * 4);
17586
- colors.set(data.tint, vertex * 3);
18170
+ colors.set(tint, vertex * 3);
17587
18171
  }
17588
18172
  outputIndices.set([start, start + 1, start + 2, start, start + 2, start + 3], indexOffset);
17589
18173
  indexOffset += 6;
@@ -17650,7 +18234,7 @@ var SectionWorker = _SectionWorker;
17650
18234
 
17651
18235
  // src/world/SectionMesh.ts
17652
18236
  var templateCache = /* @__PURE__ */ new WeakMap();
17653
- var WATER_TINT = new import_three42.Color(4159204).toArray();
18237
+ var WATER_TINT = new import_three43.Color(4159204).toArray();
17654
18238
  function toInput(entries, maxAtlasSize, fluids) {
17655
18239
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
17656
18240
  const atlases = [];
@@ -17724,7 +18308,7 @@ function toInput(entries, maxAtlasSize, fluids) {
17724
18308
  input.cullMasks[i] = cullMask;
17725
18309
  }
17726
18310
  if (fluids) {
17727
- input.fluids = { cells: fluids.cells };
18311
+ input.fluids = { cells: fluids.cells, waterTints: fluids.waterTints };
17728
18312
  for (const kind of ["water", "lava"]) {
17729
18313
  const atlas = fluids[kind];
17730
18314
  if (!atlas) continue;
@@ -17745,7 +18329,7 @@ function toInput(entries, maxAtlasSize, fluids) {
17745
18329
  }
17746
18330
  return { input, atlases };
17747
18331
  }
17748
- var SectionMesh = class _SectionMesh extends import_three42.Group {
18332
+ var SectionMesh = class _SectionMesh extends import_three43.Group {
17749
18333
  constructor() {
17750
18334
  super(...arguments);
17751
18335
  this.ownedMeshes = [];
@@ -17770,26 +18354,26 @@ var SectionMesh = class _SectionMesh extends import_three42.Group {
17770
18354
  for (const { atlas, x, y } of page.placements) {
17771
18355
  context.drawImage(atlases[atlas].image.canvas, x, y);
17772
18356
  }
17773
- const geometry = new import_three42.BufferGeometry();
17774
- geometry.setAttribute("position", new import_three42.Float32BufferAttribute(page.positions.buffer, 3));
17775
- geometry.setAttribute("normal", new import_three42.Float32BufferAttribute(page.normals.buffer, 3));
17776
- geometry.setAttribute("uv", new import_three42.Float32BufferAttribute(page.uvs.buffer, 2));
17777
- geometry.setAttribute("uvBounds", new import_three42.Float32BufferAttribute(page.uvBounds.buffer, 4));
17778
- geometry.setAttribute("color", new import_three42.Float32BufferAttribute(page.colors.buffer, 3));
17779
- geometry.setIndex(new import_three42.Uint32BufferAttribute(page.indices.buffer, 1));
18357
+ const geometry = new import_three43.BufferGeometry();
18358
+ geometry.setAttribute("position", new import_three43.Float32BufferAttribute(page.positions.buffer, 3));
18359
+ geometry.setAttribute("normal", new import_three43.Float32BufferAttribute(page.normals.buffer, 3));
18360
+ geometry.setAttribute("uv", new import_three43.Float32BufferAttribute(page.uvs.buffer, 2));
18361
+ geometry.setAttribute("uvBounds", new import_three43.Float32BufferAttribute(page.uvBounds.buffer, 4));
18362
+ geometry.setAttribute("color", new import_three43.Float32BufferAttribute(page.colors.buffer, 3));
18363
+ geometry.setIndex(new import_three43.Uint32BufferAttribute(page.indices.buffer, 1));
17780
18364
  geometry.computeBoundingBox();
17781
18365
  geometry.computeBoundingSphere();
17782
18366
  const transparent = page.layer === 2 ? atlases[page.placements[0].atlas].hasTranslucency : page.layer === 1;
17783
18367
  const material = Materials.createShadedCanvasMaterial(canvas, transparent, false, true);
17784
- material.side = import_three42.FrontSide;
18368
+ material.side = import_three43.FrontSide;
17785
18369
  if (page.layer === 2) {
17786
- material.side = import_three42.DoubleSide;
18370
+ material.side = import_three43.DoubleSide;
17787
18371
  material.forceSinglePass = true;
17788
18372
  material.depthWrite = !material.transparent;
17789
18373
  }
17790
18374
  material.vertexColors = true;
17791
18375
  const texture = material.uniforms?.map?.value ?? material.map;
17792
- const mesh = new import_three42.Mesh(geometry, material);
18376
+ const mesh = new import_three43.Mesh(geometry, material);
17793
18377
  section.add(mesh);
17794
18378
  const unsubscribe = [];
17795
18379
  for (const { atlas: id, x, y } of page.placements) {
@@ -17825,17 +18409,20 @@ var SectionMesh = class _SectionMesh extends import_three42.Group {
17825
18409
  init_FluidGeometry();
17826
18410
  init_FluidQuads();
17827
18411
  init_CubeFace();
18412
+ var WATER_TINT2 = new import_three44.Color(4159204).toArray();
17828
18413
  var Chunk = class _Chunk {
17829
- constructor(scene, x, y, z, onBlocksChanged, sectionModels, neighborCell) {
18414
+ constructor(scene, x, y, z, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged) {
17830
18415
  this.onBlocksChanged = onBlocksChanged;
17831
18416
  this.sectionModels = sectionModels;
17832
18417
  this.neighborCell = neighborCell;
18418
+ this.biomeAt = biomeAt;
18419
+ this.onBiomesChanged = onBiomesChanged;
17833
18420
  this.data = new ChunkData();
17834
18421
  this.renderedBlocks = /* @__PURE__ */ new Map();
17835
18422
  this.sectionBlocks = /* @__PURE__ */ new Map();
17836
18423
  this.hiddenBlocks = /* @__PURE__ */ new Set();
17837
18424
  this.fluidCells = new Uint8Array(4096);
17838
- this.blockPosition = new import_three43.Vector3();
18425
+ this.blockPosition = new import_three44.Vector3();
17839
18426
  this.meshDirty = false;
17840
18427
  this.meshGeneration = 0;
17841
18428
  this.scene = scene;
@@ -17845,21 +18432,71 @@ var Chunk = class _Chunk {
17845
18432
  }
17846
18433
  getBlockAt(posOrX, y, z) {
17847
18434
  if (typeof posOrX == "number") {
17848
- return this.getBlockAt(new import_three43.Vector3(posOrX, y, z));
18435
+ return this.getBlockAt(new import_three44.Vector3(posOrX, y, z));
17849
18436
  }
17850
18437
  if (isTripleArray(posOrX)) {
17851
- return this.getBlockAt(new import_three43.Vector3(posOrX[0], posOrX[1], posOrX[2]));
18438
+ return this.getBlockAt(new import_three44.Vector3(posOrX[0], posOrX[1], posOrX[2]));
17852
18439
  }
17853
18440
  const pos = this.blockPosition.set(posOrX.x - this.x * 16, posOrX.y - this.y * 16, posOrX.z - this.z * 16);
17854
18441
  const index = _Chunk.chunkPosToBlockIndex(pos);
17855
18442
  return this.renderedBlocks.get(index);
17856
18443
  }
18444
+ getBiomeAt(posOrX, y, z) {
18445
+ if (typeof posOrX === "number") return this.getBiomeAt(new import_three44.Vector3(posOrX, y, z));
18446
+ if (isTripleArray(posOrX)) return this.getBiomeAt(new import_three44.Vector3(...posOrX));
18447
+ const pos = this.worldPosToChunkPos(posOrX);
18448
+ _Chunk.chunkPosToBlockIndex(pos);
18449
+ return this.biomeAt ? this.biomeAt(posOrX.x, posOrX.y, posOrX.z) : this.biomes?.[Math.floor(pos.x / 4) + Math.floor(pos.z / 4) * 4 + Math.floor(pos.y / 4) * 16];
18450
+ }
18451
+ /** Copies 64 biome IDs in x + z * 4 + y * 16 order and refreshes placed blocks. Omit the array to clear biome data. */
18452
+ setBiomes(biomes) {
18453
+ const copy = biomes ? [...biomes] : void 0;
18454
+ if (copy && (copy.length !== 64 || !copy.every((id) => typeof id === "string" && id.length > 0))) {
18455
+ throw new RangeError("Section biomes must contain 64 nonempty biome IDs");
18456
+ }
18457
+ if (this.onBiomesChanged) return this.onBiomesChanged(copy);
18458
+ this.biomes = copy;
18459
+ return this.refreshBiomes();
18460
+ }
18461
+ /** Rebuilds biome-tinted blocks while preserving hidden blocks and unrelated render objects. */
18462
+ async refreshBiomes() {
18463
+ const affected = /* @__PURE__ */ new Map();
18464
+ const blocks = [];
18465
+ for (const index of this.renderedBlocks.keys()) {
18466
+ const block = this.data.get(index);
18467
+ const stateKey = ChunkData.paletteKey(block);
18468
+ let matches = affected.get(stateKey);
18469
+ if (!matches) {
18470
+ const key = AssetKey.parse("blockstates", block.type);
18471
+ matches = BlockStates.get(key).then(async (state) => {
18472
+ const origin = state?.key ?? key;
18473
+ const [source, fluid] = await Promise.all([BlockTints.biomeSource(origin), getBlockFluidState(origin, block.properties)]);
18474
+ return !!source || fluid?.kind === "water";
18475
+ });
18476
+ affected.set(stateKey, matches);
18477
+ }
18478
+ if (await matches) blocks.push({ index, block });
18479
+ }
18480
+ if (!blocks.length) return;
18481
+ const hidden = new Set(this.hiddenBlocks);
18482
+ try {
18483
+ await this.placeBlocks(blocks);
18484
+ } finally {
18485
+ for (const index of hidden) {
18486
+ this.hiddenBlocks.add(index);
18487
+ this.renderedBlocks.get(index)?.object?.setVisible(false);
18488
+ }
18489
+ const positions = blocks.map(({ index }) => new import_three44.Vector3(...this.positionAt(index)));
18490
+ if (this.onBlocksChanged) await this.onBlocksChanged(positions);
18491
+ else await this.rebuildSectionMesh();
18492
+ }
18493
+ }
17857
18494
  async setBlockAt(posOrX, yOrBlock, z, block) {
17858
18495
  if (typeof posOrX == "number") {
17859
- return this.setBlockAt(new import_three43.Vector3(posOrX, yOrBlock, z), block);
18496
+ return this.setBlockAt(new import_three44.Vector3(posOrX, yOrBlock, z), block);
17860
18497
  }
17861
18498
  if (isTripleArray(posOrX)) {
17862
- return this.setBlockAt(new import_three43.Vector3(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
18499
+ return this.setBlockAt(new import_three44.Vector3(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
17863
18500
  }
17864
18501
  const worldPos = posOrX;
17865
18502
  const pos = this.worldPosToChunkPos(worldPos);
@@ -17892,14 +18529,15 @@ var Chunk = class _Chunk {
17892
18529
  nbt: block.nbt === void 0 ? void 0 : structuredClone(block.nbt)
17893
18530
  } : void 0
17894
18531
  }));
17895
- const states = new Map(placements.filter(({ key }) => key !== void 0).map(({ key, block }) => [key, block]));
17896
- const settled = await Promise.allSettled([...states.values()].map(async (stored) => {
17897
- const blockState3 = await BlockStates.get(AssetKey.parse("blockstates", stored.type));
17898
- const perBlock = !blockState3 || !this.sectionModels || !!(blockState3.key && BlockEntities.entry(await BlockEntities.getIndex(blockState3.key.root), blockState3.key.toNamespacedString()));
17899
- const fluid = await getBlockFluidState(blockState3?.key, stored.properties);
17900
- const prepared = perBlock || fluid?.renderModel === false ? void 0 : await this.sectionModels.prepareState(blockState3, stored.properties);
17901
- return { blockState: blockState3, perBlock, fluid, prepared, stored };
17902
- }));
18532
+ const states = /* @__PURE__ */ new Map();
18533
+ for (const { index, key, block } of placements) {
18534
+ if (key === void 0) continue;
18535
+ let state = states.get(key);
18536
+ if (!state) states.set(key, state = { stored: block, indices: [] });
18537
+ state.indices.push(index);
18538
+ }
18539
+ const biomes = /* @__PURE__ */ new Map();
18540
+ const settled = await Promise.allSettled([...states.values()].map(({ stored, indices }) => this.resolveState(stored, indices, biomes)));
17903
18541
  const resolutions = new Map([...states.keys()].map((key, i) => [key, settled[i]]));
17904
18542
  const positions = [];
17905
18543
  let failed = false, failure;
@@ -17912,7 +18550,7 @@ var Chunk = class _Chunk {
17912
18550
  if (this.fluidCells[index]) this.meshDirty = true;
17913
18551
  this.fluidCells[index] = 0;
17914
18552
  this.renderedBlocks.delete(index);
17915
- const worldPos = new import_three43.Vector3(
18553
+ const worldPos = new import_three44.Vector3(
17916
18554
  this.x * 16 + index % 16,
17917
18555
  this.y * 16 + Math.floor(index / 256),
17918
18556
  this.z * 16 + Math.floor(index / 16) % 16
@@ -17923,7 +18561,8 @@ var Chunk = class _Chunk {
17923
18561
  if (!readBlock) continue;
17924
18562
  const resolved = resolutions.get(key);
17925
18563
  if (resolved.status === "rejected") throw resolved.reason;
17926
- const { blockState: blockState3, perBlock, fluid, prepared, stored } = resolved.value;
18564
+ const { blockState: blockState3, perBlock, fluid, prepared, contexts, stored } = resolved.value;
18565
+ const context = contexts.get(index);
17927
18566
  if (!blockState3) {
17928
18567
  this.data.assign(index, 0);
17929
18568
  continue;
@@ -17931,9 +18570,10 @@ var Chunk = class _Chunk {
17931
18570
  const variantPosition = worldPos.toArray();
17932
18571
  if (fluid) {
17933
18572
  this.fluidCells[index] = (fluid.kind === "water" ? 16 : 32) | Math.min(fluid.level, 8);
18573
+ if (fluid.kind === "water") this.setWaterTint(index, context?.waterTint);
17934
18574
  this.meshDirty = true;
17935
18575
  }
17936
- const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : prepared?.pick(variantPosition);
18576
+ const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : (context?.prepared ?? prepared)?.pick(variantPosition);
17937
18577
  if (templates) {
17938
18578
  this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0 })));
17939
18579
  this.meshDirty = true;
@@ -17943,7 +18583,9 @@ var Chunk = class _Chunk {
17943
18583
  instanceMeshes: true,
17944
18584
  maxInstanceCount: 2e3,
17945
18585
  initialState: stored.properties,
17946
- variantPosition
18586
+ variantPosition,
18587
+ biome: context?.biome,
18588
+ biomePosition: this.biomePosition(index, blockState3, stored)
17947
18589
  });
17948
18590
  object4.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
17949
18591
  }
@@ -17966,24 +18608,21 @@ var Chunk = class _Chunk {
17966
18608
  for (const info of this.renderedBlocks.values()) info.object?.removeFromScene();
17967
18609
  this.sectionBlocks.clear();
17968
18610
  this.fluidCells.fill(0);
18611
+ this.waterTints = void 0;
17969
18612
  this.renderedBlocks.clear();
17970
18613
  this.hiddenBlocks.clear();
17971
18614
  this.meshDirty = true;
17972
18615
  this.data.copyFrom(data);
17973
- const ids = /* @__PURE__ */ new Set();
18616
+ const ids = /* @__PURE__ */ new Map();
17974
18617
  for (let index = 0; index < 4096; index++) {
17975
18618
  const id = this.data.idAt(index);
17976
- if (id) ids.add(id);
17977
- }
17978
- const palette = [...ids];
17979
- const settled = await Promise.allSettled(palette.map(async (id) => {
17980
- const state = this.data.stateAt(id);
17981
- const blockState3 = await BlockStates.get(AssetKey.parse("blockstates", state.type));
17982
- const perBlock = !blockState3 || !this.sectionModels || !!(blockState3.key && BlockEntities.entry(await BlockEntities.getIndex(blockState3.key.root), blockState3.key.toNamespacedString()));
17983
- const fluid = await getBlockFluidState(blockState3?.key, state.properties);
17984
- const prepared = perBlock || fluid?.renderModel === false ? void 0 : await this.sectionModels.prepareState(blockState3, state.properties);
17985
- return { blockState: blockState3, perBlock, fluid, prepared };
17986
- }));
18619
+ if (!id) continue;
18620
+ let indices = ids.get(id);
18621
+ if (!indices) ids.set(id, indices = []);
18622
+ indices.push(index);
18623
+ }
18624
+ const palette = [...ids.keys()], biomes = /* @__PURE__ */ new Map();
18625
+ const settled = await Promise.allSettled(palette.map((id) => this.resolveState(this.data.stateAt(id), ids.get(id), biomes)));
17987
18626
  const resolutions = new Map(palette.map((id, i) => [id, settled[i]]));
17988
18627
  const perBlocks = [];
17989
18628
  let failed = false, failure;
@@ -17993,24 +18632,28 @@ var Chunk = class _Chunk {
17993
18632
  try {
17994
18633
  const resolved = resolutions.get(id);
17995
18634
  if (resolved.status === "rejected") throw resolved.reason;
17996
- const { blockState: blockState3, perBlock, fluid, prepared } = resolved.value;
18635
+ const { blockState: blockState3, perBlock, fluid, prepared, contexts } = resolved.value;
18636
+ const context = contexts.get(index);
17997
18637
  if (!blockState3) {
17998
18638
  this.data.assign(index, 0);
17999
18639
  this.fluidCells[index] = 0;
18000
18640
  continue;
18001
18641
  }
18002
- if (fluid) this.fluidCells[index] = (fluid.kind === "water" ? 16 : 32) | Math.min(fluid.level, 8);
18642
+ if (fluid) {
18643
+ this.fluidCells[index] = (fluid.kind === "water" ? 16 : 32) | Math.min(fluid.level, 8);
18644
+ if (fluid.kind === "water") this.setWaterTint(index, context?.waterTint);
18645
+ }
18003
18646
  const variantPosition = [
18004
18647
  this.x * 16 + index % 16,
18005
18648
  this.y * 16 + Math.floor(index / 256),
18006
18649
  this.z * 16 + Math.floor(index / 16) % 16
18007
18650
  ];
18008
- const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : prepared?.pick(variantPosition);
18651
+ const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : (context?.prepared ?? prepared)?.pick(variantPosition);
18009
18652
  if (templates) {
18010
18653
  this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0 })));
18011
18654
  this.meshDirty = true;
18012
18655
  } else {
18013
- perBlocks.push({ index, blockState: blockState3 });
18656
+ perBlocks.push({ index, blockState: blockState3, biome: context?.biome });
18014
18657
  }
18015
18658
  const readBlock = this.data.snapshot(index);
18016
18659
  this.renderedBlocks.set(index, { get block() {
@@ -18023,7 +18666,7 @@ var Chunk = class _Chunk {
18023
18666
  failed = true;
18024
18667
  }
18025
18668
  }
18026
- for (const { index, blockState: blockState3 } of perBlocks) {
18669
+ for (const { index, blockState: blockState3, biome } of perBlocks) {
18027
18670
  let object4;
18028
18671
  try {
18029
18672
  object4 = await this.scene.addBlock(blockState3, {
@@ -18031,13 +18674,11 @@ var Chunk = class _Chunk {
18031
18674
  instanceMeshes: true,
18032
18675
  maxInstanceCount: 2e3,
18033
18676
  initialState: this.data.stateAt(this.data.idAt(index)).properties,
18034
- variantPosition: [
18035
- this.x * 16 + index % 16,
18036
- this.y * 16 + Math.floor(index / 256),
18037
- this.z * 16 + Math.floor(index / 16) % 16
18038
- ]
18677
+ variantPosition: this.positionAt(index),
18678
+ biome,
18679
+ biomePosition: this.biomePosition(index, blockState3, this.data.stateAt(this.data.idAt(index)))
18039
18680
  });
18040
- object4.setPosition(new import_three43.Vector3(
18681
+ object4.setPosition(new import_three44.Vector3(
18041
18682
  this.x * 256 + index % 16 * 16,
18042
18683
  this.y * 256 + Math.floor(index / 256) * 16,
18043
18684
  this.z * 256 + Math.floor(index / 16) % 16 * 16
@@ -18054,6 +18695,68 @@ var Chunk = class _Chunk {
18054
18695
  }
18055
18696
  if (failed) throw failure;
18056
18697
  }
18698
+ positionAt(index) {
18699
+ return [this.x * 16 + index % 16, this.y * 16 + Math.floor(index / 256), this.z * 16 + Math.floor(index / 16) % 16];
18700
+ }
18701
+ biomePosition(index, blockState3, stored) {
18702
+ const position = this.positionAt(index), name = blockState3.key?.toNamespacedString();
18703
+ if (stored.properties?.half === "upper" && (name === "minecraft:tall_grass" || name === "minecraft:large_fern")) position[1]--;
18704
+ return position;
18705
+ }
18706
+ async resolveState(stored, indices, biomes) {
18707
+ const blockState3 = await BlockStates.get(AssetKey.parse("blockstates", stored.type));
18708
+ const perBlock = !blockState3 || !this.sectionModels || !!(blockState3.key && BlockEntities.entry(await BlockEntities.getIndex(blockState3.key.root), blockState3.key.toNamespacedString()));
18709
+ const fluid = await getBlockFluidState(blockState3?.key, stored.properties);
18710
+ 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;
18711
+ const source = hasBiomes && blockState3 ? await BlockTints.biomeSource(blockState3.key) : void 0;
18712
+ const prepare = (color2) => perBlock || fluid?.renderModel === false ? void 0 : this.sectionModels.prepareState(blockState3, stored.properties, color2);
18713
+ const contexts = /* @__PURE__ */ new Map();
18714
+ const groups = /* @__PURE__ */ new Map();
18715
+ if (blockState3 && (source || fluid?.kind === "water")) {
18716
+ for (const index of indices) {
18717
+ const position = this.biomePosition(index, blockState3, stored);
18718
+ const biome = this.biomeAt ? this.biomeAt(...position) : position[1] >= this.y * 16 ? this.getBiomeAt(position) : void 0;
18719
+ if (!biome) continue;
18720
+ const key = JSON.stringify(source === "grass" ? [biome, position[0], position[2]] : [biome]);
18721
+ let group = groups.get(key);
18722
+ if (!group) groups.set(key, group = { biome, position, indices: [] });
18723
+ group.indices.push(index);
18724
+ }
18725
+ }
18726
+ const groupedCount = [...groups.values()].reduce((sum, group) => sum + group.indices.length, 0);
18727
+ const prepared = groupedCount < indices.length ? await prepare() : void 0;
18728
+ await Promise.all([...groups.values()].map(async ({ biome, position, indices: indices2 }) => {
18729
+ const available = () => {
18730
+ const key = JSON.stringify([biome, blockState3?.key?.root]);
18731
+ let found = biomes.get(key);
18732
+ if (!found) {
18733
+ found = Biomes.get(biome, blockState3?.key).then((value) => value !== void 0);
18734
+ biomes.set(key, found);
18735
+ }
18736
+ return found;
18737
+ };
18738
+ const context = { biome };
18739
+ if (perBlock) {
18740
+ if (!await available()) context.biome = void 0;
18741
+ } else {
18742
+ context.prepared = await prepare(async () => await available() ? BlockTints.getBiomeColor(blockState3?.key, biome, position) : void 0);
18743
+ if (fluid?.kind === "water" && await available()) {
18744
+ const color2 = await Biomes.getColor(biome, "water", blockState3?.key);
18745
+ if (color2 !== void 0 && color2 !== 4159204) context.waterTint = new import_three44.Color(color2).toArray();
18746
+ }
18747
+ }
18748
+ for (const index of indices2) contexts.set(index, context);
18749
+ }));
18750
+ return { blockState: blockState3, perBlock, fluid, prepared, contexts, stored };
18751
+ }
18752
+ setWaterTint(index, tint) {
18753
+ if (!this.sectionModels || !this.waterTints && tint === void 0) return;
18754
+ if (!this.waterTints) {
18755
+ this.waterTints = new Float32Array(4096 * 3);
18756
+ for (let cell = 0; cell < 4096; cell++) this.waterTints.set(WATER_TINT2, cell * 3);
18757
+ }
18758
+ this.waterTints.set(tint ?? WATER_TINT2, index * 3);
18759
+ }
18057
18760
  /** Changes visibility at world block coordinates while preserving the block's data. */
18058
18761
  async setBlockVisibleAt(pos, visible) {
18059
18762
  const index = _Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos));
@@ -18157,7 +18860,7 @@ var Chunk = class _Chunk {
18157
18860
  if (block && !block.object) origins.set(kind, block.block.type);
18158
18861
  }
18159
18862
  if (origins.size) {
18160
- fluids = { cells: new Uint8Array(18 * 18 * 18) };
18863
+ fluids = { cells: new Uint8Array(18 * 18 * 18), waterTints: this.waterTints?.slice() };
18161
18864
  for (let y = -1; y <= 16; y++) {
18162
18865
  for (let z = -1; z <= 16; z++) {
18163
18866
  for (let x = -1; x <= 16; x++) {
@@ -18188,7 +18891,7 @@ var Chunk = class _Chunk {
18188
18891
  /** Removes stored blocks and render objects. Set `notify` to false to skip the owning world's update. */
18189
18892
  async clear(notify = true) {
18190
18893
  const positions = notify && this.onBlocksChanged ? [...this.renderedBlocks.keys()].map((index) => this.chunkPosToWorldPos(
18191
- new import_three43.Vector3(index % 16, Math.floor(index / 256), Math.floor(index / 16) % 16)
18894
+ new import_three44.Vector3(index % 16, Math.floor(index / 256), Math.floor(index / 16) % 16)
18192
18895
  )) : [];
18193
18896
  for (const info of this.renderedBlocks.values()) info.object?.removeFromScene();
18194
18897
  this.sectionBlocks.clear();
@@ -18199,8 +18902,13 @@ var Chunk = class _Chunk {
18199
18902
  this.renderedBlocks.clear();
18200
18903
  this.hiddenBlocks.clear();
18201
18904
  this.fluidCells.fill(0);
18905
+ this.waterTints = void 0;
18202
18906
  this.data.clear();
18203
- if (notify) await this.onBlocksChanged?.(positions);
18907
+ this.biomes = void 0;
18908
+ if (notify) {
18909
+ await this.onBiomesChanged?.(void 0);
18910
+ await this.onBlocksChanged?.(positions);
18911
+ }
18204
18912
  }
18205
18913
  async dispose() {
18206
18914
  await this.clear();
@@ -18216,14 +18924,14 @@ var Chunk = class _Chunk {
18216
18924
  return pos.y * 16 * 16 + pos.z * 16 + pos.x;
18217
18925
  }
18218
18926
  worldPosToChunkPos(pos) {
18219
- return new import_three43.Vector3(
18927
+ return new import_three44.Vector3(
18220
18928
  pos.x - this.x * 16,
18221
18929
  pos.y - this.y * 16,
18222
18930
  pos.z - this.z * 16
18223
18931
  );
18224
18932
  }
18225
18933
  chunkPosToWorldPos(pos) {
18226
- return new import_three43.Vector3(
18934
+ return new import_three44.Vector3(
18227
18935
  this.x * 16 + pos.x,
18228
18936
  this.y * 16 + pos.y,
18229
18937
  this.z * 16 + pos.z