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.
@@ -324,6 +324,9 @@ var init_Caching = __esm({
324
324
  static {
325
325
  this.usernameUuidCache = import_loading_cache.Caches.builder().expireAfterWrite(import_time.Time.minutes(5)).expirationInterval(import_time.Time.seconds(30)).buildAsync();
326
326
  }
327
+ static {
328
+ this.biomeCache = _Caching.createAssetCache();
329
+ }
327
330
  static createAssetCache() {
328
331
  return import_loading_cache.Caches.builder().expireAfterWrite(import_time.Time.minutes(10)).expireAfterAccess(import_time.Time.minutes(5)).expirationInterval(import_time.Time.seconds(30)).buildAsync();
329
332
  }
@@ -353,7 +356,8 @@ var init_Caching = __esm({
353
356
  this.fontCache,
354
357
  this.bannerPatternCache,
355
358
  this.mineRenderDataCache,
356
- this.usernameUuidCache
359
+ this.usernameUuidCache,
360
+ this.biomeCache
357
361
  ];
358
362
  }
359
363
  /** Invalidates in-memory entries so later lookups reload them. Persisted entries are retained. */
@@ -1731,7 +1735,7 @@ ${key}` : key, async () => {
1731
1735
  case "blockStates":
1732
1736
  return Object.values(value).every((property) => object(property) && "default" in property && Array.isArray(property.values));
1733
1737
  case "blockTints":
1734
- return ["constant", "grass", "stem", "redstone"].includes(value.source) && (value.source !== "constant" || Number.isInteger(value.color));
1738
+ 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));
1735
1739
  case "fluids":
1736
1740
  return ["water", "lava"].includes(value.kind) && typeof value.renderModel === "boolean" && (value.levelProperty === void 0 || typeof value.levelProperty === "string");
1737
1741
  case "dyes":
@@ -1875,15 +1879,19 @@ var init_Colormaps = __esm({
1875
1879
  Colormaps = class {
1876
1880
  /** Samples the selected resource pack's grass colormap using Minecraft's climate coordinates. */
1877
1881
  static async grassColor(origin, temperature = 0.5, downfall = 1) {
1878
- const key = AssetKey.parse("textures", "minecraft:colormap/grass", origin);
1882
+ return this.getColor("grass", origin, temperature, downfall);
1883
+ }
1884
+ /** Samples a resource-pack colormap at the supplied climate coordinates. */
1885
+ static async getColor(kind, origin, temperature = 0.5, downfall = 1) {
1886
+ const key = AssetKey.parse("textures", `minecraft:colormap/${kind}`, origin);
1879
1887
  const temp = Math.fround(temperature);
1880
1888
  const rain = Math.fround(downfall) * temp;
1881
1889
  const x = Math.floor((1 - temp) * 255), y = Math.floor((1 - rain) * 255);
1882
1890
  return await Caching.blockTintCache.get(`${key.serialize()}|${x},${y}`, async () => {
1883
1891
  const image = await ModelTextures.get(key);
1884
- if (!image) throw new MineRenderError(`Missing grass colormap: ${key.toNamespacedString()}`);
1892
+ if (!image) throw new MineRenderError(`Missing ${kind} colormap: ${key.toNamespacedString()}`);
1885
1893
  if (image.width !== 256 || image.height !== 256) {
1886
- throw new MineRenderError("Grass colormaps must be 256 \xD7 256 pixels");
1894
+ throw new MineRenderError(`${kind} colormaps must be 256 \xD7 256 pixels`);
1887
1895
  }
1888
1896
  const [red, green, blue] = image.data.getImageData(x, y, 1, 1).data;
1889
1897
  return red << 16 | green << 8 | blue;
@@ -7614,6 +7622,7 @@ __export(index_node_exports, {
7614
7622
  BannerPatterns: () => BannerPatterns,
7615
7623
  BasicAssetKey: () => BasicAssetKey,
7616
7624
  BatchedExecutor: () => BatchedExecutor,
7625
+ Biomes: () => Biomes,
7617
7626
  BlockEntities: () => BlockEntities,
7618
7627
  BlockObject: () => BlockObject,
7619
7628
  BlockStateResolver: () => BlockStateResolver,
@@ -7645,6 +7654,7 @@ __export(index_node_exports, {
7645
7654
  EntityObject: () => EntityObject,
7646
7655
  Env: () => Env,
7647
7656
  FluidModelObject: () => FluidModelObject,
7657
+ FlyControls: () => FlyControls,
7648
7658
  Fonts: () => Fonts,
7649
7659
  GUI_CONTAINER_LAYOUTS: () => GUI_CONTAINER_LAYOUTS,
7650
7660
  Geometries: () => Geometries,
@@ -7750,6 +7760,7 @@ __export(index_node_exports, {
7750
7760
  isDisposable: () => isDisposable,
7751
7761
  isDoubleArray: () => isDoubleArray,
7752
7762
  isEntityObject: () => isEntityObject,
7763
+ isFlyControls: () => isFlyControls,
7753
7764
  isGuiObject: () => isGuiObject,
7754
7765
  isInstanceManager: () => isInstanceManager,
7755
7766
  isInstanceReference: () => isInstanceReference,
@@ -7954,6 +7965,116 @@ init_AssetLoader();
7954
7965
  init_Assets();
7955
7966
  init_BannerPatterns();
7956
7967
 
7968
+ // src/assets/Biomes.ts
7969
+ init_AssetKey();
7970
+ init_AssetLoader();
7971
+ init_AssetParsers();
7972
+ init_Caching();
7973
+ init_MineRenderError();
7974
+ init_Colormaps();
7975
+
7976
+ // src/assets/_biomes/SwampGrassColor.ts
7977
+ var permutation;
7978
+ 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]];
7979
+ var skew = (Math.sqrt(3) - 1) / 2;
7980
+ var unskew = (3 - Math.sqrt(3)) / 6;
7981
+ function createPermutation() {
7982
+ const multiplier = 0x5deece66dn, mask = (1n << 48n) - 1n;
7983
+ let seed = 2345n ^ multiplier;
7984
+ const next = (bits) => {
7985
+ seed = seed * multiplier + 11n & mask;
7986
+ return Number(seed >> BigInt(48 - bits));
7987
+ };
7988
+ for (let i = 0; i < 3; i++) {
7989
+ next(26);
7990
+ next(27);
7991
+ }
7992
+ const values = Uint8Array.from({ length: 256 }, (_, i) => i);
7993
+ for (let i = 0; i < 256; i++) {
7994
+ const bound = 256 - i;
7995
+ let offset;
7996
+ if ((bound & bound - 1) === 0) offset = Math.floor(bound * next(31) / 2147483648);
7997
+ else {
7998
+ let bits;
7999
+ do {
8000
+ bits = next(31);
8001
+ offset = bits % bound;
8002
+ } while (bits - offset + bound - 1 > 2147483647);
8003
+ }
8004
+ const other = i + offset;
8005
+ [values[i], values[other]] = [values[other], values[i]];
8006
+ }
8007
+ return values;
8008
+ }
8009
+ function swampGrassColor(x, z) {
8010
+ const p10 = permutation ??= createPermutation();
8011
+ x *= 0.0225;
8012
+ z *= 0.0225;
8013
+ const s = (x + z) * skew;
8014
+ const i = Math.floor(x + s), j = Math.floor(z + s);
8015
+ const t = (i + j) * unskew;
8016
+ const dx = x - (i - t), dz = z - (j - t);
8017
+ const stepX = dx > dz ? 1 : 0, stepZ = 1 - stepX;
8018
+ const corner = (gx, gz, a, b) => {
8019
+ let falloff = 0.5 - a * a - b * b;
8020
+ if (falloff < 0) return 0;
8021
+ const gradient = gradients[p10[gx + p10[gz & 255] & 255] % 12];
8022
+ falloff *= falloff;
8023
+ return falloff * falloff * (gradient[0] * a + gradient[1] * b);
8024
+ };
8025
+ 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));
8026
+ return noise < -0.1 ? 5011004 : 6975545;
8027
+ }
8028
+
8029
+ // src/assets/Biomes.ts
8030
+ var Biomes = class {
8031
+ /** Loads a biome registry entry, or returns `undefined` when no source provides it. */
8032
+ static async get(id, origin) {
8033
+ if (!/^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(id)) throw new MineRenderError(`Invalid biome identifier: ${id}`);
8034
+ const [namespace, path] = id.includes(":") ? id.split(":") : ["minecraft", id];
8035
+ const key = new AssetKey(namespace, path, "worldgen", "biome", "data", ".json", origin?.root);
8036
+ return Caching.biomeCache.get(key.serialize(), async () => {
8037
+ const asset2 = await AssetLoader.get(key, "json" /* JSON */);
8038
+ if (asset2 === void 0) return void 0;
8039
+ if (!asset2 || typeof asset2 !== "object" || Array.isArray(asset2) || !asset2.effects || typeof asset2.effects !== "object" || Array.isArray(asset2.effects)) {
8040
+ throw new MineRenderError(`Invalid biome effects: ${id}`);
8041
+ }
8042
+ if (typeof asset2.temperature !== "number" || !Number.isFinite(asset2.temperature) || typeof asset2.downfall !== "number" || !Number.isFinite(asset2.downfall)) {
8043
+ throw new MineRenderError(`Invalid biome climate: ${id}`);
8044
+ }
8045
+ for (const kind of ["grass", "foliage", "dry_foliage", "water"]) {
8046
+ const color2 = asset2.effects[`${kind}_color`];
8047
+ if (color2 !== void 0) this.parseColor(color2, id, kind);
8048
+ }
8049
+ const modifier = asset2.effects.grass_color_modifier;
8050
+ if (modifier !== void 0 && modifier !== "none" && modifier !== "dark_forest" && modifier !== "swamp") {
8051
+ throw new MineRenderError(`Invalid biome grass color modifier: ${id}`);
8052
+ }
8053
+ return asset2;
8054
+ });
8055
+ }
8056
+ /** Returns packed sRGB, or `undefined` when the biome or requested water color is missing. */
8057
+ static async getColor(id, kind, origin, x = 0, z = 0) {
8058
+ const biome = await this.get(id, origin);
8059
+ if (!biome) return void 0;
8060
+ const rawColor = biome.effects[`${kind}_color`];
8061
+ let color2 = rawColor === void 0 ? void 0 : this.parseColor(rawColor, id, kind);
8062
+ if (kind === "water") return color2;
8063
+ const modifier = kind === "grass" ? biome.effects.grass_color_modifier : void 0;
8064
+ if (modifier === "swamp") return swampGrassColor(x, z);
8065
+ if (color2 === void 0) {
8066
+ const clamp = (value) => Math.max(0, Math.min(1, Math.fround(value)));
8067
+ color2 = await Colormaps.getColor(kind, origin, clamp(biome.temperature), clamp(biome.downfall));
8068
+ }
8069
+ return modifier === "dark_forest" ? (color2 & 16711422) + 2634762 >> 1 : color2;
8070
+ }
8071
+ static parseColor(value, id, kind) {
8072
+ if (typeof value === "string" && /^#[0-9a-fA-F]{6}$/.test(value)) return parseInt(value.slice(1), 16);
8073
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 16777215) return value;
8074
+ throw new MineRenderError(`Invalid biome ${kind} color: ${id}`);
8075
+ }
8076
+ };
8077
+
7957
8078
  // src/assets/BlockEntities.ts
7958
8079
  var import_three10 = require("three");
7959
8080
  init_AssetKey();
@@ -12392,8 +12513,20 @@ var BlockStateResolver = class _BlockStateResolver {
12392
12513
  init_Colormaps();
12393
12514
  init_MineRenderData();
12394
12515
  var BlockTints = class {
12395
- /** Resolves block preview colors without biome context; explicit tints take precedence. */
12396
- static async get(key, state, model, tints) {
12516
+ /** Returns a block's versioned biome color source, leaving fixed-color blocks unchanged. */
12517
+ static async biomeSource(key) {
12518
+ const rule = key && (await MineRenderData.get("blockTints", key.root))[key.toNamespacedString()];
12519
+ return rule ? rule.biome ?? (rule.source === "grass" ? "grass" : void 0) : void 0;
12520
+ }
12521
+ /** Resolves one block's biome tint at world block coordinates, without biome blending. */
12522
+ static async getBiomeColor(key, biome, position) {
12523
+ if (!biome) return void 0;
12524
+ const x = position?.[0], z = position?.[2];
12525
+ const source = await this.biomeSource(key);
12526
+ return source ? Biomes.getColor(biome, source, key, x, z) : void 0;
12527
+ }
12528
+ /** Resolves used tint indices from biome or preview colors; explicit tints take precedence. */
12529
+ static async get(key, state, model, tints, biomeColor) {
12397
12530
  if (!key) return tints;
12398
12531
  const candidates = (model.elements ?? []).flatMap((element) => Object.values(element.faces)).some((face) => face?.tintindex !== void 0 && face.tintindex >= 0 && tints?.[face.tintindex] === void 0);
12399
12532
  if (!candidates) return tints;
@@ -12409,8 +12542,12 @@ var BlockTints = class {
12409
12542
  }
12410
12543
  }
12411
12544
  if (!indices.size) return tints;
12545
+ const source = rule.biome ?? (rule.source === "grass" ? "grass" : void 0);
12546
+ const biome = source ? typeof biomeColor === "function" ? await biomeColor() : biomeColor : void 0;
12412
12547
  let color2;
12413
- switch (rule.source) {
12548
+ if (biome !== void 0) {
12549
+ color2 = biome;
12550
+ } else switch (rule.source) {
12414
12551
  case "constant":
12415
12552
  color2 = rule.color;
12416
12553
  break;
@@ -12469,6 +12606,7 @@ var BlockObject = class _BlockObject extends SceneObject {
12469
12606
  this._entityGeneration = 0;
12470
12607
  this.options = (0, import_ts_deepmerge6.default)({}, _BlockObject.DEFAULT_OPTIONS, options ?? {});
12471
12608
  this.variantPosition = options?.variantPosition ? [...options.variantPosition] : void 0;
12609
+ this.biomePosition = options?.biomePosition ? [...options.biomePosition] : void 0;
12472
12610
  }
12473
12611
  static {
12474
12612
  this.DEFAULT_OPTIONS = (0, import_ts_deepmerge6.default)({}, ModelObject.DEFAULT_OPTIONS, {
@@ -12666,11 +12804,16 @@ var BlockObject = class _BlockObject extends SceneObject {
12666
12804
  const { FluidModelObject: FluidModelObject2, sampleFluid: sampleFluid2 } = await Promise.resolve().then(() => (init_FluidModelObject(), FluidModelObject_exports));
12667
12805
  this._fluidSampler = sample;
12668
12806
  const surface = sampleFluid2(kind, (x, y, z) => x === 0 && y === 0 && z === 0 ? { fluid: kind, level: this.fluidLevel } : sample?.(x, y, z) ?? {});
12669
- if (this._fluidKey === surface.key) return;
12807
+ const tint = this.options.tints?.[0] ?? this.waterTint;
12808
+ const fluidKey = `${surface.key}|${tint ?? "default"}`;
12809
+ if (this._fluidKey === fluidKey) return;
12670
12810
  const key = new AssetKey("minecraft", `${kind}/${surface.key}`, "models", "fluid", "assets", ".json", this.blockState.key?.root);
12671
12811
  const replacement = await this.scene.addSceneObject(
12672
12812
  { key },
12673
- () => new FluidModelObject2(kind, surface.sample, this.blockState.key, this.options)
12813
+ () => new FluidModelObject2(kind, surface.sample, this.blockState.key, {
12814
+ ...this.options,
12815
+ tints: tint === void 0 ? this.options.tints : { ...this.options.tints, 0: tint }
12816
+ })
12674
12817
  );
12675
12818
  const previous = this._fluidModel;
12676
12819
  const matrix = previous ? this.getModelMatrix(previous) : new import_three34.Matrix4().makeTranslation(...this.position.toArray());
@@ -12679,7 +12822,7 @@ var BlockObject = class _BlockObject extends SceneObject {
12679
12822
  this._models = this._models.filter((model) => model !== previous);
12680
12823
  this._models.push(replacement);
12681
12824
  this._fluidModel = replacement;
12682
- this._fluidKey = surface.key;
12825
+ this._fluidKey = fluidKey;
12683
12826
  this._isInstanced ||= isInstanceReference(replacement);
12684
12827
  this._instanceCounter = this._isInstanced ? 1 : 0;
12685
12828
  if (previous) this.removeModels([previous]);
@@ -12702,6 +12845,7 @@ var BlockObject = class _BlockObject extends SceneObject {
12702
12845
  }
12703
12846
  async recreateModels() {
12704
12847
  this.clearModels();
12848
+ 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;
12705
12849
  if (resolveBlockFluidState(this.blockState.key, this.state, this._fluidRules)?.renderModel === false) {
12706
12850
  await this.updateFluid(this._fluidSampler);
12707
12851
  return;
@@ -12736,7 +12880,13 @@ var BlockObject = class _BlockObject extends SceneObject {
12736
12880
  const model = await Models.getMerged(modelKey);
12737
12881
  const options = {
12738
12882
  ...this.options,
12739
- tints: model ? await BlockTints.get(this.blockState.key, this.state, model, this.options.tints) : this.options.tints,
12883
+ tints: model ? await BlockTints.get(
12884
+ this.blockState.key,
12885
+ this.state,
12886
+ model,
12887
+ this.options.tints,
12888
+ () => BlockTints.getBiomeColor(this.blockState.key, this.options.biome, this.biomePosition)
12889
+ ) : this.options.tints,
12740
12890
  uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0) ? [rotation.x, rotation.y, rotation.z] : void 0,
12741
12891
  cullMask: this.options.displayPosition ? 0 : ModelCulling.toLocalMask(this._cullMask, rotation)
12742
12892
  };
@@ -13319,8 +13469,323 @@ init_ModelObject();
13319
13469
  // src/index.ts
13320
13470
  init_nbt();
13321
13471
 
13322
- // src/renderer/MineRenderScene.ts
13472
+ // src/renderer/FlyControls.ts
13323
13473
  var import_three35 = require("three");
13474
+ var VANILLA_FLY_SPEED = 10.89;
13475
+ var PITCH_LIMIT = Math.PI / 2;
13476
+ var MOVE_EPSILON = 1e-6;
13477
+ var _euler = new import_three35.Euler(0, 0, 0, "YXZ");
13478
+ var _forward = new import_three35.Vector3();
13479
+ var _right = new import_three35.Vector3();
13480
+ var _targetVelocity = new import_three35.Vector3();
13481
+ var FlyControls = class extends import_three35.Controls {
13482
+ constructor(camera, domElement) {
13483
+ super(camera, domElement ?? null);
13484
+ this.isFlyControls = true;
13485
+ /** Horizontal and vertical speed in scene units per second; vanilla creative flight by default. */
13486
+ this.movementSpeed = 16 * VANILLA_FLY_SPEED;
13487
+ /** Speed multiplier while the sprint key is held. */
13488
+ this.sprintMultiplier = 2;
13489
+ /** Radians per pixel of pointer movement. */
13490
+ this.lookSpeed = 2e-3;
13491
+ /** Smooth acceleration and deceleration, approximating vanilla flight momentum. */
13492
+ this.enableDamping = true;
13493
+ /** Portion of the remaining velocity change applied per 60 Hz frame (0–1) when damping is enabled. */
13494
+ this.dampingFactor = 0.25;
13495
+ /** Capture the pointer on click so the mouse turns the camera without dragging; dragging works regardless. */
13496
+ this.pointerLock = true;
13497
+ /** Scroll the wheel to change `movementSpeed` within `speedRange`. */
13498
+ this.enableWheelSpeed = true;
13499
+ /** Minimum and maximum `movementSpeed` reachable with the wheel. */
13500
+ this.speedRange = [16, 16 * 500];
13501
+ /** Touch joystick travel in CSS pixels for full speed. */
13502
+ this.joystickRadius = 64;
13503
+ /** Key bindings by `KeyboardEvent.code`. */
13504
+ this.keys = {
13505
+ forward: ["KeyW", "ArrowUp"],
13506
+ back: ["KeyS", "ArrowDown"],
13507
+ left: ["KeyA", "ArrowLeft"],
13508
+ right: ["KeyD", "ArrowRight"],
13509
+ up: ["Space"],
13510
+ down: ["ShiftLeft", "ShiftRight"],
13511
+ sprint: ["ControlLeft", "ControlRight"]
13512
+ };
13513
+ this._pressed = /* @__PURE__ */ new Set();
13514
+ this._velocity = new import_three35.Vector3();
13515
+ this._lastPosition = new import_three35.Vector3();
13516
+ this._lastQuaternion = new import_three35.Quaternion();
13517
+ this._position0 = new import_three35.Vector3();
13518
+ this._quaternion0 = new import_three35.Quaternion();
13519
+ this._dragMoved = false;
13520
+ this._locked = false;
13521
+ this._onKeyDown = (event) => this.handleKey(event, true);
13522
+ this._onKeyUp = (event) => this.handleKey(event, false);
13523
+ this._onPointerDown = (event) => this.handlePointerDown(event);
13524
+ this._onPointerMove = (event) => this.handlePointerMove(event);
13525
+ this._onPointerUp = (event) => this.handlePointerUp(event);
13526
+ this._onPointerLockChange = () => this.handlePointerLockChange();
13527
+ this._onContextMenu = (event) => event.preventDefault();
13528
+ this._onWheel = (event) => this.handleWheel(event);
13529
+ this._onBlur = () => this.releaseKeys();
13530
+ this._lastPosition.copy(camera.position);
13531
+ this._lastQuaternion.copy(camera.quaternion);
13532
+ this.saveState();
13533
+ if (domElement) this.connect(domElement);
13534
+ }
13535
+ connect(element) {
13536
+ super.connect(element);
13537
+ const document2 = element.ownerDocument;
13538
+ document2.addEventListener("keydown", this._onKeyDown);
13539
+ document2.addEventListener("keyup", this._onKeyUp);
13540
+ document2.addEventListener("pointerlockchange", this._onPointerLockChange);
13541
+ document2.defaultView?.addEventListener("blur", this._onBlur);
13542
+ element.addEventListener("pointerdown", this._onPointerDown);
13543
+ element.addEventListener("pointermove", this._onPointerMove);
13544
+ element.addEventListener("pointerup", this._onPointerUp);
13545
+ element.addEventListener("pointercancel", this._onPointerUp);
13546
+ element.addEventListener("contextmenu", this._onContextMenu);
13547
+ element.addEventListener("wheel", this._onWheel, { passive: false });
13548
+ element.addEventListener("blur", this._onBlur);
13549
+ element.style.touchAction = "none";
13550
+ if (element.tabIndex < 0) element.tabIndex = 0;
13551
+ }
13552
+ disconnect() {
13553
+ const element = this.domElement;
13554
+ if (!element) return;
13555
+ const document2 = element.ownerDocument;
13556
+ document2.removeEventListener("keydown", this._onKeyDown);
13557
+ document2.removeEventListener("keyup", this._onKeyUp);
13558
+ document2.removeEventListener("pointerlockchange", this._onPointerLockChange);
13559
+ document2.defaultView?.removeEventListener("blur", this._onBlur);
13560
+ element.removeEventListener("pointerdown", this._onPointerDown);
13561
+ element.removeEventListener("pointermove", this._onPointerMove);
13562
+ element.removeEventListener("pointerup", this._onPointerUp);
13563
+ element.removeEventListener("pointercancel", this._onPointerUp);
13564
+ element.removeEventListener("contextmenu", this._onContextMenu);
13565
+ element.removeEventListener("wheel", this._onWheel);
13566
+ element.removeEventListener("blur", this._onBlur);
13567
+ element.style.touchAction = "auto";
13568
+ this.unlock();
13569
+ this._locked = false;
13570
+ this.releaseKeys();
13571
+ this._dragPointer = void 0;
13572
+ this._joystick = void 0;
13573
+ }
13574
+ dispose() {
13575
+ this.disconnect();
13576
+ }
13577
+ /** Whether mouse look currently owns the pointer. */
13578
+ get locked() {
13579
+ return this._locked;
13580
+ }
13581
+ /** Requests pointer capture; browsers only allow this from a user gesture. */
13582
+ lock() {
13583
+ const element = this.domElement;
13584
+ if (!element || this._locked || typeof element.requestPointerLock !== "function") return;
13585
+ try {
13586
+ const result = element.requestPointerLock();
13587
+ if (result instanceof Promise) result.catch(() => void 0);
13588
+ } catch {
13589
+ }
13590
+ }
13591
+ /** Releases a captured pointer. */
13592
+ unlock() {
13593
+ const document2 = this.domElement?.ownerDocument;
13594
+ if (document2?.pointerLockElement && document2.pointerLockElement === this.domElement) {
13595
+ document2.exitPointerLock();
13596
+ }
13597
+ }
13598
+ /** Stores the camera pose that {@link reset} restores. */
13599
+ saveState() {
13600
+ this._position0.copy(this.object.position);
13601
+ this._quaternion0.copy(this.object.quaternion);
13602
+ }
13603
+ /** Restores the saved camera pose and stops movement. */
13604
+ reset() {
13605
+ this.object.position.copy(this._position0);
13606
+ this.object.quaternion.copy(this._quaternion0);
13607
+ this._velocity.set(0, 0, 0);
13608
+ this.notifyChange();
13609
+ }
13610
+ /** Horizontal yaw in radians, where zero looks toward -Z. */
13611
+ get yaw() {
13612
+ return _euler.setFromQuaternion(this.object.quaternion).y;
13613
+ }
13614
+ /** Vertical pitch in radians, positive looking up. */
13615
+ get pitch() {
13616
+ return _euler.setFromQuaternion(this.object.quaternion).x;
13617
+ }
13618
+ /** Turns the camera; pitch is clamped to straight up and down. */
13619
+ setRotation(yaw, pitch) {
13620
+ _euler.set(import_three35.MathUtils.clamp(pitch, -PITCH_LIMIT, PITCH_LIMIT), yaw, 0);
13621
+ this.object.quaternion.setFromEuler(_euler);
13622
+ this.notifyChange();
13623
+ }
13624
+ /** Points the camera at a position without rolling it. */
13625
+ lookAt(target) {
13626
+ _forward.copy(target).sub(this.object.position);
13627
+ if (_forward.lengthSq() < MOVE_EPSILON) return;
13628
+ _forward.normalize();
13629
+ this.setRotation(Math.atan2(-_forward.x, -_forward.z), Math.asin(import_three35.MathUtils.clamp(_forward.y, -1, 1)));
13630
+ }
13631
+ /** Whether an action's key is held. */
13632
+ isPressed(action) {
13633
+ return this._pressed.has(action);
13634
+ }
13635
+ /**
13636
+ * Advances movement by `delta` seconds (measured since the previous call when omitted)
13637
+ * and dispatches `change` when the camera moved.
13638
+ */
13639
+ update(delta) {
13640
+ if (delta === void 0) {
13641
+ const now = typeof performance !== "undefined" ? performance.now() : Date.now();
13642
+ delta = this._lastTime === void 0 ? 0 : (now - this._lastTime) / 1e3;
13643
+ this._lastTime = now;
13644
+ }
13645
+ if (!this.enabled) {
13646
+ this._velocity.set(0, 0, 0);
13647
+ return;
13648
+ }
13649
+ if (delta > 0) this.move(delta);
13650
+ this.notifyChange();
13651
+ }
13652
+ move(delta) {
13653
+ const camera = this.object;
13654
+ let forward = (this._pressed.has("forward") ? 1 : 0) - (this._pressed.has("back") ? 1 : 0);
13655
+ let strafe = (this._pressed.has("right") ? 1 : 0) - (this._pressed.has("left") ? 1 : 0);
13656
+ const climb = (this._pressed.has("up") ? 1 : 0) - (this._pressed.has("down") ? 1 : 0);
13657
+ const joystick = this._joystick;
13658
+ if (joystick) {
13659
+ const radius = Math.max(this.joystickRadius, 1);
13660
+ forward = import_three35.MathUtils.clamp(forward + (joystick.originY - joystick.y) / radius, -1, 1);
13661
+ strafe = import_three35.MathUtils.clamp(strafe + (joystick.x - joystick.originX) / radius, -1, 1);
13662
+ }
13663
+ _euler.setFromQuaternion(camera.quaternion);
13664
+ const yaw = _euler.y;
13665
+ _forward.set(-Math.sin(yaw), 0, -Math.cos(yaw));
13666
+ _right.set(Math.cos(yaw), 0, -Math.sin(yaw));
13667
+ _targetVelocity.set(0, 0, 0);
13668
+ if (forward || strafe || climb) {
13669
+ _targetVelocity.addScaledVector(_forward, forward).addScaledVector(_right, strafe);
13670
+ if (_targetVelocity.lengthSq() > 1) _targetVelocity.normalize();
13671
+ _targetVelocity.y = climb;
13672
+ const speed = this.movementSpeed * (this._pressed.has("sprint") ? this.sprintMultiplier : 1);
13673
+ _targetVelocity.multiplyScalar(speed);
13674
+ }
13675
+ if (this.enableDamping) {
13676
+ const blend = 1 - Math.pow(1 - import_three35.MathUtils.clamp(this.dampingFactor, 0, 1), delta * 60);
13677
+ this._velocity.lerp(_targetVelocity, blend);
13678
+ if (_targetVelocity.lengthSq() === 0 && this._velocity.lengthSq() < MOVE_EPSILON) this._velocity.set(0, 0, 0);
13679
+ } else {
13680
+ this._velocity.copy(_targetVelocity);
13681
+ }
13682
+ camera.position.addScaledVector(this._velocity, delta);
13683
+ }
13684
+ notifyChange() {
13685
+ const camera = this.object;
13686
+ if (this._lastPosition.distanceToSquared(camera.position) > MOVE_EPSILON || 8 * (1 - this._lastQuaternion.dot(camera.quaternion)) > MOVE_EPSILON) {
13687
+ this._lastPosition.copy(camera.position);
13688
+ this._lastQuaternion.copy(camera.quaternion);
13689
+ this.dispatchEvent({ type: "change" });
13690
+ }
13691
+ }
13692
+ releaseKeys() {
13693
+ this._pressed.clear();
13694
+ }
13695
+ handleKey(event, down) {
13696
+ const action = this.actionFor(event.code);
13697
+ if (!action) return;
13698
+ const ownsInput = this.enabled && (this._locked || event.target === this.domElement);
13699
+ if (down && !ownsInput) return;
13700
+ const pressed = this._pressed.has(action);
13701
+ if (down) this._pressed.add(action);
13702
+ else this._pressed.delete(action);
13703
+ if (ownsInput || this.enabled && pressed) event.preventDefault();
13704
+ }
13705
+ actionFor(code) {
13706
+ for (const action in this.keys) {
13707
+ if (this.keys[action].includes(code)) return action;
13708
+ }
13709
+ return void 0;
13710
+ }
13711
+ handlePointerDown(event) {
13712
+ const element = this.domElement;
13713
+ if (!this.enabled || !element) return;
13714
+ element.focus?.({ preventScroll: true });
13715
+ if (this._locked) return;
13716
+ if (event.pointerType === "touch" && !this._joystick && this.onJoystickSide(event)) {
13717
+ this._joystick = { id: event.pointerId, originX: event.clientX, originY: event.clientY, x: event.clientX, y: event.clientY };
13718
+ element.setPointerCapture?.(event.pointerId);
13719
+ return;
13720
+ }
13721
+ if (this._dragPointer !== void 0) return;
13722
+ if (event.pointerType === "mouse" && event.button !== 0 && event.button !== 2) return;
13723
+ this._dragPointer = event.pointerId;
13724
+ this._dragMoved = false;
13725
+ element.setPointerCapture?.(event.pointerId);
13726
+ }
13727
+ onJoystickSide(event) {
13728
+ const rect = this.domElement?.getBoundingClientRect?.();
13729
+ return !!rect && event.clientX < rect.left + rect.width / 2;
13730
+ }
13731
+ handlePointerMove(event) {
13732
+ if (!this.enabled) return;
13733
+ const joystick = this._joystick;
13734
+ if (joystick && event.pointerId === joystick.id) {
13735
+ joystick.x = event.clientX;
13736
+ joystick.y = event.clientY;
13737
+ return;
13738
+ }
13739
+ if (!this._locked && event.pointerId !== this._dragPointer) return;
13740
+ const dx = event.movementX ?? 0;
13741
+ const dy = event.movementY ?? 0;
13742
+ if (!dx && !dy) return;
13743
+ this._dragMoved = true;
13744
+ this.turn(dx, dy);
13745
+ }
13746
+ handlePointerUp(event) {
13747
+ if (this._joystick?.id === event.pointerId) {
13748
+ this._joystick = void 0;
13749
+ this.domElement?.releasePointerCapture?.(event.pointerId);
13750
+ return;
13751
+ }
13752
+ if (event.pointerId !== this._dragPointer) return;
13753
+ this._dragPointer = void 0;
13754
+ this.domElement?.releasePointerCapture?.(event.pointerId);
13755
+ if (this.enabled && this.pointerLock && !this._dragMoved && event.pointerType === "mouse" && event.button === 0) {
13756
+ this.lock();
13757
+ }
13758
+ }
13759
+ handleWheel(event) {
13760
+ if (!this.enabled || !this.enableWheelSpeed) return;
13761
+ event.preventDefault();
13762
+ const steps = event.deltaMode === 0 ? event.deltaY / 100 : event.deltaY;
13763
+ const [min, max] = this.speedRange;
13764
+ this.movementSpeed = import_three35.MathUtils.clamp(this.movementSpeed * Math.pow(1.15, -steps), Math.min(min, max), Math.max(min, max));
13765
+ }
13766
+ handlePointerLockChange() {
13767
+ const document2 = this.domElement?.ownerDocument;
13768
+ const locked = !!document2 && document2.pointerLockElement === this.domElement;
13769
+ if (locked === this._locked) return;
13770
+ this._locked = locked;
13771
+ if (!locked) this.releaseKeys();
13772
+ this.dispatchEvent({ type: locked ? "lock" : "unlock" });
13773
+ }
13774
+ turn(dx, dy) {
13775
+ _euler.setFromQuaternion(this.object.quaternion);
13776
+ _euler.y -= dx * this.lookSpeed;
13777
+ _euler.x = import_three35.MathUtils.clamp(_euler.x - dy * this.lookSpeed, -PITCH_LIMIT, PITCH_LIMIT);
13778
+ _euler.z = 0;
13779
+ this.object.quaternion.setFromEuler(_euler);
13780
+ this.notifyChange();
13781
+ }
13782
+ };
13783
+ function isFlyControls(obj) {
13784
+ return obj?.isFlyControls === true;
13785
+ }
13786
+
13787
+ // src/renderer/MineRenderScene.ts
13788
+ var import_three36 = require("three");
13324
13789
  init_SceneObject();
13325
13790
  var import_ts_deepmerge8 = __toESM(require_dist());
13326
13791
  init_ModelObject();
@@ -14230,7 +14695,7 @@ var SkinObject = class extends SceneObject {
14230
14695
  // src/renderer/MineRenderScene.ts
14231
14696
  init_EntityObject();
14232
14697
  init_ItemTints();
14233
- var MineRenderScene = class _MineRenderScene extends import_three35.Scene {
14698
+ var MineRenderScene = class _MineRenderScene extends import_three36.Scene {
14234
14699
  constructor(options) {
14235
14700
  super();
14236
14701
  this.isMineRenderScene = true;
@@ -14386,7 +14851,7 @@ function isMineRenderScene(obj) {
14386
14851
  }
14387
14852
 
14388
14853
  // src/renderer/Renderer.ts
14389
- var import_three37 = require("three");
14854
+ var import_three38 = require("three");
14390
14855
  var import_ts_deepmerge9 = __toESM(require_dist());
14391
14856
  var import_stats = __toESM(require("stats.js"));
14392
14857
  var import_postprocessing = require("postprocessing");
@@ -14395,11 +14860,11 @@ init_Model();
14395
14860
  init_three();
14396
14861
 
14397
14862
  // src/three/OrbitControls.js
14398
- var import_three36 = require("three");
14863
+ var import_three37 = require("three");
14399
14864
  var _changeEvent = { type: "change" };
14400
14865
  var _startEvent = { type: "start" };
14401
14866
  var _endEvent = { type: "end" };
14402
- var OrbitControls = class extends import_three36.EventDispatcher {
14867
+ var OrbitControls = class extends import_three37.EventDispatcher {
14403
14868
  constructor(object4, domElement) {
14404
14869
  super();
14405
14870
  if (domElement === void 0) console.warn('THREE.OrbitControls: The second parameter "domElement" is now mandatory.');
@@ -14408,7 +14873,7 @@ var OrbitControls = class extends import_three36.EventDispatcher {
14408
14873
  this.domElement = domElement;
14409
14874
  this.domElement.style.touchAction = "none";
14410
14875
  this.enabled = true;
14411
- this.target = new import_three36.Vector3();
14876
+ this.target = new import_three37.Vector3();
14412
14877
  this.minDistance = 0;
14413
14878
  this.maxDistance = Infinity;
14414
14879
  this.minZoom = 0;
@@ -14430,8 +14895,8 @@ var OrbitControls = class extends import_three36.EventDispatcher {
14430
14895
  this.autoRotate = false;
14431
14896
  this.autoRotateSpeed = 2;
14432
14897
  this.keys = { LEFT: "ArrowLeft", UP: "ArrowUp", RIGHT: "ArrowRight", BOTTOM: "ArrowDown" };
14433
- this.mouseButtons = { LEFT: import_three36.MOUSE.ROTATE, MIDDLE: import_three36.MOUSE.DOLLY, RIGHT: import_three36.MOUSE.PAN };
14434
- this.touches = { ONE: import_three36.TOUCH.ROTATE, TWO: import_three36.TOUCH.DOLLY_PAN };
14898
+ this.mouseButtons = { LEFT: import_three37.MOUSE.ROTATE, MIDDLE: import_three37.MOUSE.DOLLY, RIGHT: import_three37.MOUSE.PAN };
14899
+ this.touches = { ONE: import_three37.TOUCH.ROTATE, TWO: import_three37.TOUCH.DOLLY_PAN };
14435
14900
  this.target0 = this.target.clone();
14436
14901
  this.position0 = this.object.position.clone();
14437
14902
  this.zoom0 = this.object.zoom;
@@ -14460,11 +14925,11 @@ var OrbitControls = class extends import_three36.EventDispatcher {
14460
14925
  state = STATE.NONE;
14461
14926
  };
14462
14927
  this.update = function() {
14463
- const offset = new import_three36.Vector3();
14464
- const quat = new import_three36.Quaternion().setFromUnitVectors(object4.up, new import_three36.Vector3(0, 1, 0));
14928
+ const offset = new import_three37.Vector3();
14929
+ const quat = new import_three37.Quaternion().setFromUnitVectors(object4.up, new import_three37.Vector3(0, 1, 0));
14465
14930
  const quatInverse = quat.clone().invert();
14466
- const lastPosition = new import_three36.Vector3();
14467
- const lastQuaternion = new import_three36.Quaternion();
14931
+ const lastPosition = new import_three37.Vector3();
14932
+ const lastQuaternion = new import_three37.Quaternion();
14468
14933
  const twoPI = 2 * Math.PI;
14469
14934
  return function update() {
14470
14935
  const position = scope.object.position;
@@ -14550,20 +15015,20 @@ var OrbitControls = class extends import_three36.EventDispatcher {
14550
15015
  };
14551
15016
  let state = STATE.NONE;
14552
15017
  const EPS = 1e-6;
14553
- const spherical = new import_three36.Spherical();
14554
- const sphericalDelta = new import_three36.Spherical();
15018
+ const spherical = new import_three37.Spherical();
15019
+ const sphericalDelta = new import_three37.Spherical();
14555
15020
  let scale = 1;
14556
- const panOffset = new import_three36.Vector3();
15021
+ const panOffset = new import_three37.Vector3();
14557
15022
  let zoomChanged = false;
14558
- const rotateStart = new import_three36.Vector2();
14559
- const rotateEnd = new import_three36.Vector2();
14560
- const rotateDelta = new import_three36.Vector2();
14561
- const panStart = new import_three36.Vector2();
14562
- const panEnd = new import_three36.Vector2();
14563
- const panDelta = new import_three36.Vector2();
14564
- const dollyStart = new import_three36.Vector2();
14565
- const dollyEnd = new import_three36.Vector2();
14566
- const dollyDelta = new import_three36.Vector2();
15023
+ const rotateStart = new import_three37.Vector2();
15024
+ const rotateEnd = new import_three37.Vector2();
15025
+ const rotateDelta = new import_three37.Vector2();
15026
+ const panStart = new import_three37.Vector2();
15027
+ const panEnd = new import_three37.Vector2();
15028
+ const panDelta = new import_three37.Vector2();
15029
+ const dollyStart = new import_three37.Vector2();
15030
+ const dollyEnd = new import_three37.Vector2();
15031
+ const dollyDelta = new import_three37.Vector2();
14567
15032
  const pointers = [];
14568
15033
  const pointerPositions = {};
14569
15034
  function getAutoRotationAngle() {
@@ -14579,7 +15044,7 @@ var OrbitControls = class extends import_three36.EventDispatcher {
14579
15044
  sphericalDelta.phi -= angle;
14580
15045
  }
14581
15046
  const panLeft = function() {
14582
- const v = new import_three36.Vector3();
15047
+ const v = new import_three37.Vector3();
14583
15048
  return function panLeft2(distance, objectMatrix) {
14584
15049
  v.setFromMatrixColumn(objectMatrix, 0);
14585
15050
  v.multiplyScalar(-distance);
@@ -14587,7 +15052,7 @@ var OrbitControls = class extends import_three36.EventDispatcher {
14587
15052
  };
14588
15053
  }();
14589
15054
  const panUp = function() {
14590
- const v = new import_three36.Vector3();
15055
+ const v = new import_three37.Vector3();
14591
15056
  return function panUp2(distance, objectMatrix) {
14592
15057
  if (scope.screenSpacePanning === true) {
14593
15058
  v.setFromMatrixColumn(objectMatrix, 1);
@@ -14600,7 +15065,7 @@ var OrbitControls = class extends import_three36.EventDispatcher {
14600
15065
  };
14601
15066
  }();
14602
15067
  const pan = function() {
14603
- const offset = new import_three36.Vector3();
15068
+ const offset = new import_three37.Vector3();
14604
15069
  return function pan2(deltaX, deltaY) {
14605
15070
  const element = scope.domElement;
14606
15071
  if (scope.object.isPerspectiveCamera) {
@@ -14849,12 +15314,12 @@ var OrbitControls = class extends import_three36.EventDispatcher {
14849
15314
  mouseAction = -1;
14850
15315
  }
14851
15316
  switch (mouseAction) {
14852
- case import_three36.MOUSE.DOLLY:
15317
+ case import_three37.MOUSE.DOLLY:
14853
15318
  if (scope.enableZoom === false) return;
14854
15319
  handleMouseDownDolly(event);
14855
15320
  state = STATE.DOLLY;
14856
15321
  break;
14857
- case import_three36.MOUSE.ROTATE:
15322
+ case import_three37.MOUSE.ROTATE:
14858
15323
  if (event.ctrlKey || event.metaKey || event.shiftKey) {
14859
15324
  if (scope.enablePan === false) return;
14860
15325
  handleMouseDownPan(event);
@@ -14865,7 +15330,7 @@ var OrbitControls = class extends import_three36.EventDispatcher {
14865
15330
  state = STATE.ROTATE;
14866
15331
  }
14867
15332
  break;
14868
- case import_three36.MOUSE.PAN:
15333
+ case import_three37.MOUSE.PAN:
14869
15334
  if (event.ctrlKey || event.metaKey || event.shiftKey) {
14870
15335
  if (scope.enableRotate === false) return;
14871
15336
  handleMouseDownRotate(event);
@@ -14921,12 +15386,12 @@ var OrbitControls = class extends import_three36.EventDispatcher {
14921
15386
  switch (pointers.length) {
14922
15387
  case 1:
14923
15388
  switch (scope.touches.ONE) {
14924
- case import_three36.TOUCH.ROTATE:
15389
+ case import_three37.TOUCH.ROTATE:
14925
15390
  if (scope.enableRotate === false) return;
14926
15391
  handleTouchStartRotate();
14927
15392
  state = STATE.TOUCH_ROTATE;
14928
15393
  break;
14929
- case import_three36.TOUCH.PAN:
15394
+ case import_three37.TOUCH.PAN:
14930
15395
  if (scope.enablePan === false) return;
14931
15396
  handleTouchStartPan();
14932
15397
  state = STATE.TOUCH_PAN;
@@ -14937,12 +15402,12 @@ var OrbitControls = class extends import_three36.EventDispatcher {
14937
15402
  break;
14938
15403
  case 2:
14939
15404
  switch (scope.touches.TWO) {
14940
- case import_three36.TOUCH.DOLLY_PAN:
15405
+ case import_three37.TOUCH.DOLLY_PAN:
14941
15406
  if (scope.enableZoom === false && scope.enablePan === false) return;
14942
15407
  handleTouchStartDollyPan();
14943
15408
  state = STATE.TOUCH_DOLLY_PAN;
14944
15409
  break;
14945
- case import_three36.TOUCH.DOLLY_ROTATE:
15410
+ case import_three37.TOUCH.DOLLY_ROTATE:
14946
15411
  if (scope.enableZoom === false && scope.enableRotate === false) return;
14947
15412
  handleTouchStartDollyRotate();
14948
15413
  state = STATE.TOUCH_DOLLY_ROTATE;
@@ -15009,7 +15474,7 @@ var OrbitControls = class extends import_three36.EventDispatcher {
15009
15474
  function trackPointer(event) {
15010
15475
  let position = pointerPositions[event.pointerId];
15011
15476
  if (position === void 0) {
15012
- position = new import_three36.Vector2();
15477
+ position = new import_three37.Vector2();
15013
15478
  pointerPositions[event.pointerId] = position;
15014
15479
  }
15015
15480
  position.set(event.pageX, event.pageY);
@@ -15029,10 +15494,10 @@ var MapControls = class extends OrbitControls {
15029
15494
  constructor(object4, domElement) {
15030
15495
  super(object4, domElement);
15031
15496
  this.screenSpacePanning = false;
15032
- this.mouseButtons.LEFT = import_three36.MOUSE.PAN;
15033
- this.mouseButtons.RIGHT = import_three36.MOUSE.ROTATE;
15034
- this.touches.ONE = import_three36.TOUCH.PAN;
15035
- this.touches.TWO = import_three36.TOUCH.DOLLY_ROTATE;
15497
+ this.mouseButtons.LEFT = import_three37.MOUSE.PAN;
15498
+ this.mouseButtons.RIGHT = import_three37.MOUSE.ROTATE;
15499
+ this.touches.ONE = import_three37.TOUCH.PAN;
15500
+ this.touches.TWO = import_three37.TOUCH.DOLLY_ROTATE;
15036
15501
  }
15037
15502
  };
15038
15503
 
@@ -15111,8 +15576,8 @@ var Renderer = class _Renderer {
15111
15576
  top: void 0,
15112
15577
  bottom: void 0
15113
15578
  },
15114
- position: new import_three37.Vector3(50, 50, 50),
15115
- lookingAt: new import_three37.Vector3(0, 0, 0)
15579
+ position: new import_three38.Vector3(50, 50, 50),
15580
+ lookingAt: new import_three38.Vector3(0, 0, 0)
15116
15581
  },
15117
15582
  render: {
15118
15583
  fpsLimit: 60,
@@ -15127,7 +15592,8 @@ var Renderer = class _Renderer {
15127
15592
  enabled: true
15128
15593
  },
15129
15594
  controls: {
15130
- enabled: false
15595
+ enabled: false,
15596
+ mode: "orbit"
15131
15597
  },
15132
15598
  debug: {
15133
15599
  grid: false,
@@ -15143,14 +15609,14 @@ var Renderer = class _Renderer {
15143
15609
  switch (this.options.camera.type) {
15144
15610
  case "perspective":
15145
15611
  default:
15146
- return new import_three37.PerspectiveCamera(
15612
+ return new import_three38.PerspectiveCamera(
15147
15613
  this.options.camera.perspective.fov,
15148
15614
  this.options.camera.perspective.aspect ?? this.viewWidth / this.viewHeight,
15149
15615
  this.options.camera.near,
15150
15616
  this.options.camera.far
15151
15617
  );
15152
15618
  case "orthographic":
15153
- return new import_three37.OrthographicCamera(
15619
+ return new import_three38.OrthographicCamera(
15154
15620
  this.options.camera.orthographic.left ?? this.viewWidth / -2,
15155
15621
  this.options.camera.orthographic.right ?? this.viewWidth / 2,
15156
15622
  this.options.camera.orthographic.top ?? this.viewHeight / 2,
@@ -15166,7 +15632,7 @@ var Renderer = class _Renderer {
15166
15632
  }
15167
15633
  const pixelRatio = this.options.render.pixelRatio ?? 1;
15168
15634
  this._surface?.resize?.(Math.floor(this.viewWidth * pixelRatio), Math.floor(this.viewHeight * pixelRatio));
15169
- const renderer = new import_three37.WebGLRenderer({
15635
+ const renderer = new import_three38.WebGLRenderer({
15170
15636
  canvas: this._surface?.canvas,
15171
15637
  context: this._surface?.context,
15172
15638
  antialias: this.options.render.antialias,
@@ -15176,8 +15642,8 @@ var Renderer = class _Renderer {
15176
15642
  });
15177
15643
  renderer.setClearColor(0, 0);
15178
15644
  renderer.shadowMap.enabled = true;
15179
- renderer.shadowMap.type = import_three37.PCFShadowMap;
15180
- renderer.outputColorSpace = import_three37.SRGBColorSpace;
15645
+ renderer.shadowMap.type = import_three38.PCFShadowMap;
15646
+ renderer.outputColorSpace = import_three38.SRGBColorSpace;
15181
15647
  if (this._surface) {
15182
15648
  renderer.setDrawingBufferSize(this.viewWidth, this.viewHeight, pixelRatio);
15183
15649
  } else {
@@ -15195,20 +15661,77 @@ var Renderer = class _Renderer {
15195
15661
  }
15196
15662
  createControls() {
15197
15663
  if (!this.options.controls?.enabled) return void 0;
15198
- const position = this.camera.position.clone();
15199
- const controls = new OrbitControls(this.camera, this.renderer.domElement);
15200
- this.camera.position.copy(position);
15664
+ return this.createControlsFor(this.options.controls.mode ?? "orbit", this.initialTarget());
15665
+ }
15666
+ initialTarget() {
15201
15667
  const target = this.options.camera.lookingAt;
15202
- if (isVector3(target)) {
15668
+ if (isVector3(target)) return target.clone();
15669
+ if (isTripleArray(target)) return new import_three38.Vector3(target[0], target[1], target[2]);
15670
+ return new import_three38.Vector3();
15671
+ }
15672
+ createControlsFor(mode, target) {
15673
+ let controls;
15674
+ if (mode === "fly") {
15675
+ controls = new FlyControls(this.camera, this.renderer.domElement);
15676
+ controls.lookAt(target);
15677
+ controls.saveState();
15678
+ } else {
15679
+ const position = this.camera.position.clone();
15680
+ controls = new OrbitControls(this.camera, this.renderer.domElement);
15681
+ this.camera.position.copy(position);
15203
15682
  controls.target.copy(target);
15204
- } else if (isTripleArray(target)) {
15205
- controls.target.set(target[0], target[1], target[2]);
15683
+ controls.update();
15684
+ controls.saveState();
15206
15685
  }
15207
- controls.update();
15208
- controls.saveState();
15686
+ this._controlsTime = void 0;
15209
15687
  this.registerEventDispatcher(controls);
15210
15688
  return controls;
15211
15689
  }
15690
+ /**
15691
+ * Replaces renderer-owned controls with the selected mode, keeping the camera where it is.
15692
+ * Switching to orbit controls targets the point the camera looks at, at the previous orbit distance.
15693
+ * Returns the new controls, or undefined when controls were disabled at construction or after disposal.
15694
+ */
15695
+ setControlsMode(mode) {
15696
+ const current = this._controls;
15697
+ if (this._disposed || !current) return void 0;
15698
+ if (this.controlsMode === mode) return current;
15699
+ let target;
15700
+ if (isFlyControls(current)) {
15701
+ const distance = this._orbitDistance ?? (this.camera.position.distanceTo(this.initialTarget()) || 1);
15702
+ target = new import_three38.Vector3(0, 0, -distance).applyQuaternion(this.camera.quaternion).add(this.camera.position);
15703
+ } else {
15704
+ target = current.target.clone();
15705
+ this._orbitDistance = this.camera.position.distanceTo(target);
15706
+ }
15707
+ const enabled = current.enabled;
15708
+ this.disposeControls(current);
15709
+ this._controls = this.createControlsFor(mode, target);
15710
+ this._controls.enabled = enabled;
15711
+ this.dirty = true;
15712
+ return this._controls;
15713
+ }
15714
+ disposeControls(controls) {
15715
+ this._controls = void 0;
15716
+ this.unregisterEventDispatcher(controls);
15717
+ controls.enabled = false;
15718
+ controls.dispose();
15719
+ }
15720
+ /** Advances owned controls; without a frame time, flight movement does not progress. */
15721
+ updateControls(time) {
15722
+ const controls = this._controls;
15723
+ if (!controls?.enabled) {
15724
+ this._controlsTime = void 0;
15725
+ return;
15726
+ }
15727
+ if (!isFlyControls(controls)) {
15728
+ controls.update();
15729
+ return;
15730
+ }
15731
+ const previous = this._controlsTime;
15732
+ if (time !== void 0) this._controlsTime = time;
15733
+ controls.update(time === void 0 || previous === void 0 ? 0 : (time - previous) / 1e3);
15734
+ }
15212
15735
  //</editor-fold>
15213
15736
  init() {
15214
15737
  if (this._disposed) return;
@@ -15216,20 +15739,20 @@ var Renderer = class _Renderer {
15216
15739
  window["__THREE_DEVTOOLS__"].dispatchEvent(new CustomEvent("observe", { detail: this.scene }));
15217
15740
  }
15218
15741
  if (this.options.debug.grid) {
15219
- const gridHelper = new import_three37.GridHelper(128, 16);
15742
+ const gridHelper = new import_three38.GridHelper(128, 16);
15220
15743
  this._debugHelpers.push(gridHelper);
15221
15744
  this.scene.add(gridHelper);
15222
- const gridHelper2 = new import_three37.GridHelper(128, 16);
15745
+ const gridHelper2 = new import_three38.GridHelper(128, 16);
15223
15746
  this._debugHelpers.push(gridHelper2);
15224
15747
  gridHelper2.rotation.x = 90 * (Math.PI / 180);
15225
15748
  this.scene.add(gridHelper2);
15226
- const gridHelper3 = new import_three37.GridHelper(128, 16);
15749
+ const gridHelper3 = new import_three38.GridHelper(128, 16);
15227
15750
  this._debugHelpers.push(gridHelper3);
15228
15751
  gridHelper3.rotation.z = 90 * (Math.PI / 180);
15229
15752
  this.scene.add(gridHelper3);
15230
15753
  }
15231
15754
  if (this.options.debug.axes) {
15232
- const axesHelper = new import_three37.AxesHelper(64);
15755
+ const axesHelper = new import_three38.AxesHelper(64);
15233
15756
  this._debugHelpers.push(axesHelper);
15234
15757
  this.scene.add(axesHelper);
15235
15758
  }
@@ -15285,6 +15808,12 @@ var Renderer = class _Renderer {
15285
15808
  events.add(changeEvent2);
15286
15809
  dispatcher.addEventListener(changeEvent2, this._changeListener);
15287
15810
  }
15811
+ unregisterEventDispatcher(dispatcher) {
15812
+ const events = this._eventDispatchers.get(dispatcher);
15813
+ if (!events) return;
15814
+ for (const event of events) dispatcher.removeEventListener(event, this._changeListener);
15815
+ this._eventDispatchers.delete(dispatcher);
15816
+ }
15288
15817
  /** Updates the camera and canvas size. Width and height are in CSS pixels. */
15289
15818
  resize(width, height) {
15290
15819
  if (this._disposed) return;
@@ -15361,6 +15890,7 @@ var Renderer = class _Renderer {
15361
15890
  this.renderer.setAnimationLoop(null);
15362
15891
  }
15363
15892
  this._nextFrameTime = void 0;
15893
+ this._controlsTime = void 0;
15364
15894
  for (const subscription of this._frameCallbacks.values()) subscription.previous = void 0;
15365
15895
  }
15366
15896
  /**
@@ -15390,10 +15920,7 @@ var Renderer = class _Renderer {
15390
15920
  }
15391
15921
  this._element = void 0;
15392
15922
  if (this._controls) {
15393
- const controls = this._controls;
15394
- this._controls = void 0;
15395
- controls.enabled = false;
15396
- controls.dispose();
15923
+ this.disposeControls(this._controls);
15397
15924
  }
15398
15925
  this.scene.clear();
15399
15926
  for (const helper of this._debugHelpers) {
@@ -15410,9 +15937,7 @@ var Renderer = class _Renderer {
15410
15937
  }
15411
15938
  animate(t = performance.now()) {
15412
15939
  if (!this._running) return;
15413
- if (this._controls?.enabled) {
15414
- this._controls.update();
15415
- }
15940
+ this.updateControls(t);
15416
15941
  if (!this._running) return;
15417
15942
  if (!this.dirty && !this.options.render.renderAlways && !this._frameCallbacks.size && !this._videoExporter) return;
15418
15943
  const interval = this._frameInterval;
@@ -15449,7 +15974,7 @@ var Renderer = class _Renderer {
15449
15974
  /** Draws a fresh frame without starting the animation loop or invoking frame callbacks. */
15450
15975
  renderOnce() {
15451
15976
  if (this._disposed) throw new Error("Cannot render a disposed renderer");
15452
- if (this._controls?.enabled) this._controls.update();
15977
+ this.updateControls();
15453
15978
  this.drawFrame();
15454
15979
  }
15455
15980
  /**
@@ -15479,7 +16004,7 @@ var Renderer = class _Renderer {
15479
16004
  try {
15480
16005
  if (!wasRunning) this.start();
15481
16006
  this._videoExporter = exporter;
15482
- if (this._controls?.enabled) this._controls.update();
16007
+ this.updateControls();
15483
16008
  this.drawFrame();
15484
16009
  } catch (error) {
15485
16010
  exporter.cancel(error);
@@ -15530,6 +16055,19 @@ var Renderer = class _Renderer {
15530
16055
  get controls() {
15531
16056
  return this._controls;
15532
16057
  }
16058
+ /** Renderer-owned OrbitControls, or undefined in fly mode or without controls. */
16059
+ get orbitControls() {
16060
+ return this._controls && !isFlyControls(this._controls) ? this._controls : void 0;
16061
+ }
16062
+ /** Renderer-owned {@link FlyControls}, or undefined in orbit mode or without controls. */
16063
+ get flyControls() {
16064
+ return isFlyControls(this._controls) ? this._controls : void 0;
16065
+ }
16066
+ /** The active renderer-owned controls mode, or undefined without controls. */
16067
+ get controlsMode() {
16068
+ if (!this._controls) return void 0;
16069
+ return isFlyControls(this._controls) ? "fly" : "orbit";
16070
+ }
15533
16071
  ///
15534
16072
  };
15535
16073
 
@@ -15778,7 +16316,7 @@ function parseSceneDocument(value) {
15778
16316
  }
15779
16317
 
15780
16318
  // src/scene/SceneDocumentLoader.ts
15781
- var import_three39 = require("three");
16319
+ var import_three40 = require("three");
15782
16320
  init_AssetKey();
15783
16321
  init_AssetLoader();
15784
16322
  init_BlockStates();
@@ -15839,7 +16377,7 @@ var SceneDocumentLoader = class {
15839
16377
  if (definition.minecraftVersion !== void 0 && definition.minecraftVersion !== AssetLoader.version) {
15840
16378
  throw new Error(`Scene requires Minecraft ${definition.minecraftVersion}; call AssetLoader.setVersion("${definition.minecraftVersion}") before loading it`);
15841
16379
  }
15842
- const root = new import_three39.Group();
16380
+ const root = new import_three40.Group();
15843
16381
  root.name = "MineRender scene document";
15844
16382
  const objects = [];
15845
16383
  let disposed = false;
@@ -15879,7 +16417,7 @@ var SceneDocumentLoader = class {
15879
16417
  return this.loadValidatedObject(scene, definition, parent);
15880
16418
  }
15881
16419
  static async loadValidatedObject(scene, definition, parent) {
15882
- const root = new import_three39.Group();
16420
+ const root = new import_three40.Group();
15883
16421
  root.name = definition.name ?? definition.id;
15884
16422
  root.userData.sceneObjectId = definition.id;
15885
16423
  root.position.fromArray(definition.position ?? [0, 0, 0]);
@@ -16757,12 +17295,12 @@ var AnvilWorldSource = class {
16757
17295
  };
16758
17296
 
16759
17297
  // src/world/Chunk.ts
16760
- var import_three43 = require("three");
17298
+ var import_three44 = require("three");
16761
17299
  init_BlockStates();
16762
17300
  init_AssetKey();
16763
17301
 
16764
17302
  // src/world/MineRenderWorld.ts
16765
- var import_three41 = require("three");
17303
+ var import_three42 = require("three");
16766
17304
  init_Model();
16767
17305
  init_util();
16768
17306
  init_AssetKey();
@@ -16770,7 +17308,7 @@ init_BlockStates();
16770
17308
  init_CubeFace();
16771
17309
 
16772
17310
  // src/world/SectionModels.ts
16773
- var import_three40 = require("three");
17311
+ var import_three41 = require("three");
16774
17312
  init_AssetKey();
16775
17313
  init_Models();
16776
17314
  init_CubeFace();
@@ -16794,7 +17332,7 @@ function cached(map, key, load) {
16794
17332
  }
16795
17333
  function compactQuads(geometry, keep) {
16796
17334
  const quads = keep.flatMap((kept, index) => kept ? [index] : []);
16797
- const compact = new import_three40.BufferGeometry();
17335
+ const compact = new import_three41.BufferGeometry();
16798
17336
  for (const [name, size] of Object.entries({ position: 3, normal: 3, uv: 2, uvBounds: 4, color: 3 })) {
16799
17337
  const source2 = geometry.getAttribute(name);
16800
17338
  const defaults = name === "uvBounds" ? [0, 0, 1, 1] : name === "color" ? [1, 1, 1] : void 0;
@@ -16806,7 +17344,7 @@ function compactQuads(geometry, keep) {
16806
17344
  }
16807
17345
  }
16808
17346
  }
16809
- compact.setAttribute(name, new import_three40.BufferAttribute(values, size));
17347
+ compact.setAttribute(name, new import_three41.BufferAttribute(values, size));
16810
17348
  }
16811
17349
  const source = geometry.getIndex();
16812
17350
  const indices = [];
@@ -16827,24 +17365,48 @@ var SectionModels = class {
16827
17365
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("maxAtlasSize must be a positive integer");
16828
17366
  }
16829
17367
  /** Returns templates for the matched parts, or `undefined` when the block needs an individual render object. */
16830
- async get(blockState3, properties = {}, position) {
16831
- const variantPosition = position ? [...position] : void 0;
16832
- return (await this.prepareState(blockState3, properties))?.pick(variantPosition);
17368
+ async get(blockState3, properties = {}, position, biome) {
17369
+ const biomePosition = position ? [...position] : void 0;
17370
+ const prepared = await this.state(blockState3, properties);
17371
+ if (!prepared) return void 0;
17372
+ return Promise.all(prepared.groups.map(async (group) => {
17373
+ const variant = BlockStateResolver.choose(group, biomePosition);
17374
+ const choice = prepared.choices.get(variant);
17375
+ const tints = await BlockTints.get(
17376
+ blockState3.key,
17377
+ prepared.state,
17378
+ choice.model,
17379
+ void 0,
17380
+ () => BlockTints.getBiomeColor(blockState3.key, biome, biomePosition)
17381
+ );
17382
+ return this.template(variant, choice, tints);
17383
+ }));
16833
17384
  }
16834
- /** Resolves section templates once, with a synchronous weighted choice for each cell. */
16835
- async prepareState(blockState3, properties = {}) {
17385
+ /** Resolves section templates for one tint palette, with a synchronous weighted choice for each cell. */
17386
+ async prepareState(blockState3, properties = {}, biomeColor) {
17387
+ const prepared = await this.state(blockState3, properties);
17388
+ if (!prepared) return void 0;
17389
+ let color2;
17390
+ const resolveColor = typeof biomeColor === "function" ? () => color2 ??= biomeColor() : biomeColor;
17391
+ const templates = /* @__PURE__ */ new Map();
17392
+ await Promise.all([...prepared.choices].map(async ([variant, choice]) => {
17393
+ const tints = await BlockTints.get(blockState3.key, prepared.state, choice.model, void 0, resolveColor);
17394
+ templates.set(variant, await this.template(variant, choice, tints));
17395
+ }));
17396
+ return { pick: (position) => prepared.groups.map((group) => templates.get(BlockStateResolver.choose(group, position))) };
17397
+ }
17398
+ async state(blockState3, properties) {
16836
17399
  let states = this.states.get(blockState3);
16837
17400
  if (!states) this.states.set(blockState3, states = /* @__PURE__ */ new Map());
16838
17401
  const key = JSON.stringify(Object.entries(properties).sort(([a], [b]) => a.localeCompare(b)));
16839
- const prepared = await cached(states, key, () => this.prepare(blockState3, properties));
16840
- if (!prepared) return void 0;
16841
- return { pick: (position) => prepared.groups.map((group) => prepared.templates.get(BlockStateResolver.choose(group, position))) };
17402
+ return cached(states, key, () => this.prepare(blockState3, properties));
16842
17403
  }
16843
17404
  /** Returns the shared still/flow atlas used by per-block fluid objects. */
16844
17405
  fluidAtlas(kind, root) {
16845
- return cached(this.fluidAtlases, kind, async () => {
17406
+ const key = new AssetKey("minecraft", kind, "models", "fluid", "assets", ".json", root);
17407
+ return cached(this.fluidAtlases, key.serialize(), async () => {
16846
17408
  const atlas = await UVMapper.getAtlas({
16847
- key: new AssetKey("minecraft", kind, "models", "fluid", "assets", ".json", root),
17409
+ key,
16848
17410
  textures: { still: `minecraft:block/${kind}_still`, flow: `minecraft:block/${kind}_flow` },
16849
17411
  elements: []
16850
17412
  });
@@ -16856,64 +17418,67 @@ var SectionModels = class {
16856
17418
  const state = { ...await BlockStateResolver.defaults(blockState3), ...properties };
16857
17419
  const groups = BlockStateResolver.matching(blockState3, state);
16858
17420
  if (!groups.length) return void 0;
16859
- const templates = /* @__PURE__ */ new Map();
17421
+ const prepared = /* @__PURE__ */ new Map();
16860
17422
  for (const group of groups) {
16861
17423
  const choices = Array.isArray(group) ? group : [group];
16862
17424
  if (!choices.length) return void 0;
16863
17425
  for (const variant of choices) {
16864
17426
  if (!variant.model) return void 0;
16865
- const model = await Models.getMerged(AssetKey.parse("models", variant.model));
17427
+ const modelKey = AssetKey.parse("models", variant.model);
17428
+ modelKey.root = blockState3.key?.root;
17429
+ const model = await Models.getMerged(modelKey);
16866
17430
  if (!model?.elements?.length) return void 0;
16867
17431
  const atlas = await UVMapper.getAtlas(model);
16868
17432
  const rotation = BlockStateResolver.rotation(variant);
16869
17433
  if (!atlas || atlas.image.width > this.maxAtlasSize || atlas.image.height > this.maxAtlasSize) return void 0;
16870
- const tints = await BlockTints.get(blockState3.key, state, model);
16871
- let models = this.models.get(model);
16872
- if (!models) this.models.set(model, models = /* @__PURE__ */ new Map());
16873
- const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints]);
16874
- const template = await cached(models, key, async () => {
16875
- const object4 = new SectionModel(model, {
16876
- instanceMeshes: false,
16877
- mergeMeshes: true,
16878
- tints,
16879
- uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0) ? [rotation.x, rotation.y, rotation.z] : void 0
16880
- });
16881
- await object4.init();
16882
- let geometry = object4.children[0].geometry;
16883
- geometry.applyMatrix4(new import_three40.Matrix4().makeRotationFromEuler(rotation));
16884
- object4.clear();
16885
- const keep = [];
16886
- const cullFaces = [];
16887
- for (const element of atlas.model.elements) {
16888
- for (const face of CUBE_FACES) {
16889
- const data = element.faces[face];
16890
- keep.push(data !== void 0);
16891
- if (!data) continue;
16892
- const local = CUBE_FACES.findIndex((name) => name === data.cullface);
16893
- if (local < 0) {
16894
- cullFaces.push(0);
16895
- continue;
16896
- }
16897
- const normal = new import_three40.Vector3(...CUBE_FACE_OFFSETS[local]).applyEuler(rotation);
16898
- 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);
16899
- cullFaces.push(world < 0 ? 0 : 1 << world);
16900
- }
16901
- }
16902
- geometry = compactQuads(geometry, keep);
16903
- const template2 = {
16904
- geometry,
16905
- atlas,
16906
- cullFaces: new Uint8Array(cullFaces),
16907
- occludes: ModelCulling.isOpaqueFullCube(atlas) && ModelCulling.toLocalMask(63, rotation) === 63,
16908
- layer: atlas.hasTranslucency ? 1 : 0
16909
- };
16910
- this.templates.add(template2);
16911
- return template2;
16912
- });
16913
- templates.set(variant, template);
17434
+ prepared.set(variant, { model, atlas, rotation });
16914
17435
  }
16915
17436
  }
16916
- return { groups, templates };
17437
+ return { state, groups, choices: prepared };
17438
+ }
17439
+ async template(variant, { model, atlas, rotation }, tints) {
17440
+ let models = this.models.get(model);
17441
+ if (!models) this.models.set(model, models = /* @__PURE__ */ new Map());
17442
+ const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints]);
17443
+ return cached(models, key, async () => {
17444
+ const object4 = new SectionModel(model, {
17445
+ instanceMeshes: false,
17446
+ mergeMeshes: true,
17447
+ tints,
17448
+ uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0) ? [rotation.x, rotation.y, rotation.z] : void 0
17449
+ });
17450
+ await object4.init();
17451
+ let geometry = object4.children[0].geometry;
17452
+ geometry.applyMatrix4(new import_three41.Matrix4().makeRotationFromEuler(rotation));
17453
+ object4.clear();
17454
+ const keep = [];
17455
+ const cullFaces = [];
17456
+ for (const element of atlas.model.elements) {
17457
+ for (const face of CUBE_FACES) {
17458
+ const data = element.faces[face];
17459
+ keep.push(data !== void 0);
17460
+ if (!data) continue;
17461
+ const local = CUBE_FACES.findIndex((name) => name === data.cullface);
17462
+ if (local < 0) {
17463
+ cullFaces.push(0);
17464
+ continue;
17465
+ }
17466
+ const normal = new import_three41.Vector3(...CUBE_FACE_OFFSETS[local]).applyEuler(rotation);
17467
+ 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);
17468
+ cullFaces.push(world < 0 ? 0 : 1 << world);
17469
+ }
17470
+ }
17471
+ geometry = compactQuads(geometry, keep);
17472
+ const template = {
17473
+ geometry,
17474
+ atlas,
17475
+ cullFaces: new Uint8Array(cullFaces),
17476
+ occludes: ModelCulling.isOpaqueFullCube(atlas) && ModelCulling.toLocalMask(63, rotation) === 63,
17477
+ layer: atlas.hasTranslucency ? 1 : 0
17478
+ };
17479
+ this.templates.add(template);
17480
+ return template;
17481
+ });
16917
17482
  }
16918
17483
  /** Disposes prepared geometry and clears this world's template caches. */
16919
17484
  clear() {
@@ -17090,17 +17655,17 @@ var MineRenderWorld = class {
17090
17655
  }
17091
17656
  getBlockAt(posOrX, y, z) {
17092
17657
  if (typeof posOrX == "number") {
17093
- return this.getBlockAt(new import_three41.Vector3(posOrX, y, z));
17658
+ return this.getBlockAt(new import_three42.Vector3(posOrX, y, z));
17094
17659
  }
17095
17660
  if (isTripleArray(posOrX)) {
17096
- return this.getBlockAt(new import_three41.Vector3(posOrX[0], posOrX[1], posOrX[2]));
17661
+ return this.getBlockAt(new import_three42.Vector3(posOrX[0], posOrX[1], posOrX[2]));
17097
17662
  }
17098
17663
  this.validatePosBounds(posOrX);
17099
17664
  return this.getChunkAt(posOrX)?.getBlockAt(posOrX);
17100
17665
  }
17101
17666
  getBiomeAt(posOrX, y, z) {
17102
- if (typeof posOrX === "number") return this.getBiomeAt(new import_three41.Vector3(posOrX, y, z));
17103
- if (isTripleArray(posOrX)) return this.getBiomeAt(new import_three41.Vector3(...posOrX));
17667
+ if (typeof posOrX === "number") return this.getBiomeAt(new import_three42.Vector3(posOrX, y, z));
17668
+ if (isTripleArray(posOrX)) return this.getBiomeAt(new import_three42.Vector3(...posOrX));
17104
17669
  this.validatePosBounds(posOrX);
17105
17670
  const cx = Math.floor(posOrX.x / 16), cy = Math.floor(posOrX.y / 16), cz = Math.floor(posOrX.z / 16);
17106
17671
  const lx = posOrX.x - cx * 16, ly = posOrX.y - cy * 16, lz = posOrX.z - cz * 16;
@@ -17108,10 +17673,10 @@ var MineRenderWorld = class {
17108
17673
  }
17109
17674
  async setBlockAt(posOrX, yOrBlock, z, block) {
17110
17675
  if (typeof posOrX == "number") {
17111
- return this.setBlockAt(new import_three41.Vector3(posOrX, yOrBlock, z), block);
17676
+ return this.setBlockAt(new import_three42.Vector3(posOrX, yOrBlock, z), block);
17112
17677
  }
17113
17678
  if (isTripleArray(posOrX)) {
17114
- return this.setBlockAt(new import_three41.Vector3(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
17679
+ return this.setBlockAt(new import_three42.Vector3(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
17115
17680
  }
17116
17681
  return this.placeBlock(posOrX, yOrBlock);
17117
17682
  }
@@ -17122,10 +17687,10 @@ var MineRenderWorld = class {
17122
17687
  }
17123
17688
  async setBlockVisibleAt(posOrX, yOrVisible, z, visible) {
17124
17689
  if (typeof posOrX === "number") {
17125
- return this.setBlockVisibleAt(new import_three41.Vector3(posOrX, yOrVisible, z), visible);
17690
+ return this.setBlockVisibleAt(new import_three42.Vector3(posOrX, yOrVisible, z), visible);
17126
17691
  }
17127
17692
  if (isTripleArray(posOrX)) {
17128
- return this.setBlockVisibleAt(new import_three41.Vector3(...posOrX), yOrVisible);
17693
+ return this.setBlockVisibleAt(new import_three42.Vector3(...posOrX), yOrVisible);
17129
17694
  }
17130
17695
  this.validatePosBounds(posOrX);
17131
17696
  await this.getChunkAt(posOrX)?.setBlockVisibleAt(posOrX, yOrVisible);
@@ -17151,7 +17716,7 @@ var MineRenderWorld = class {
17151
17716
  const collect = async (positions2) => {
17152
17717
  for (const pos of positions2) changes.set(pos.toArray().join(","), pos);
17153
17718
  };
17154
- const place = (block) => this.placeBlock(new import_three41.Vector3(...block.position), block, collect);
17719
+ const place = (block) => this.placeBlock(new import_three42.Vector3(...block.position), block, collect);
17155
17720
  const keys = /* @__PURE__ */ new Map();
17156
17721
  const types = /* @__PURE__ */ new Set();
17157
17722
  for (const block of multiblock.blocks) {
@@ -17167,7 +17732,7 @@ var MineRenderWorld = class {
17167
17732
  }
17168
17733
  if (!executor) {
17169
17734
  const groups2 = /* @__PURE__ */ new Map();
17170
- const position = new import_three41.Vector3();
17735
+ const position = new import_three42.Vector3();
17171
17736
  for (const block of multiblock.blocks) {
17172
17737
  const [x, y, z] = block.position;
17173
17738
  this.validatePosBounds(position.set(x, y, z));
@@ -17207,7 +17772,7 @@ var MineRenderWorld = class {
17207
17772
  try {
17208
17773
  positions = await chunk.placeBlocks(blocks);
17209
17774
  } finally {
17210
- positions ??= blocks.map(({ index }) => new import_three41.Vector3(
17775
+ positions ??= blocks.map(({ index }) => new import_three42.Vector3(
17211
17776
  chunk.x * 16 + index % 16,
17212
17777
  chunk.y * 16 + Math.floor(index / 256),
17213
17778
  chunk.z * 16 + Math.floor(index / 16) % 16
@@ -17243,10 +17808,9 @@ var MineRenderWorld = class {
17243
17808
  }
17244
17809
  try {
17245
17810
  await this.clearChunkColumn(chunk.x, chunk.z);
17246
- let sliceStart = performance.now();
17247
17811
  for (const section of chunk.sections) {
17248
17812
  if (section.biomes) {
17249
- const position = new import_three41.Vector3(chunk.x * 16, section.y * 16, chunk.z * 16);
17813
+ const position = new import_three42.Vector3(chunk.x * 16, section.y * 16, chunk.z * 16);
17250
17814
  this.validatePosBounds(position);
17251
17815
  const copy = [...section.biomes];
17252
17816
  if (copy.length !== 64 || !copy.every((id) => typeof id === "string" && id.length > 0)) {
@@ -17257,9 +17821,12 @@ var MineRenderWorld = class {
17257
17821
  if (!column) this.biomes.set(key, column = /* @__PURE__ */ new Map());
17258
17822
  column.set(section.y, copy);
17259
17823
  }
17824
+ }
17825
+ let sliceStart = performance.now();
17826
+ for (const section of chunk.sections) {
17260
17827
  if (!executor) {
17261
17828
  if (!occupiedSections.has(section.y)) continue;
17262
- const position = new import_three41.Vector3(chunk.x * 16, section.y * 16, chunk.z * 16);
17829
+ const position = new import_three42.Vector3(chunk.x * 16, section.y * 16, chunk.z * 16);
17263
17830
  this.validatePosBounds(position);
17264
17831
  const target = this.getOrCreateChunkAt(position);
17265
17832
  try {
@@ -17353,7 +17920,7 @@ var MineRenderWorld = class {
17353
17920
  this.sectionModels?.clear();
17354
17921
  }
17355
17922
  updateCulling(positions) {
17356
- const neighbor = new import_three41.Vector3();
17923
+ const neighbor = new import_three42.Vector3();
17357
17924
  for (const pos of positions) {
17358
17925
  const cx = Math.floor(pos.x / 16), cy = Math.floor(pos.y / 16), cz = Math.floor(pos.z / 16);
17359
17926
  const chunk = this._chunks.get(`${cx}_${cy}_${cz}`);
@@ -17421,18 +17988,34 @@ var MineRenderWorld = class {
17421
17988
  const key = this.worldPosToChunkKey(pos);
17422
17989
  let chunk = this._chunks.get(key);
17423
17990
  if (typeof chunk === "undefined") {
17991
+ const x = Math.floor(pos.x / 16), y = Math.floor(pos.y / 16), z = Math.floor(pos.z / 16);
17424
17992
  chunk = new Chunk(
17425
17993
  this.scene,
17426
- Math.floor(pos.x / 16),
17427
- Math.floor(pos.y / 16),
17428
- Math.floor(pos.z / 16),
17994
+ x,
17995
+ y,
17996
+ z,
17429
17997
  (positions) => this.updateCulling(positions),
17430
17998
  this.sectionModels,
17431
- (x, y, z) => {
17432
- const cx = Math.floor(x / 16), cy = Math.floor(y / 16), cz = Math.floor(z / 16);
17999
+ (x2, y2, z2) => {
18000
+ const cx = Math.floor(x2 / 16), cy = Math.floor(y2 / 16), cz = Math.floor(z2 / 16);
17433
18001
  const section = this._chunks.get(`${cx}_${cy}_${cz}`);
17434
- const index = (y - cy * 16) * 256 + (z - cz * 16) * 16 + x - cx * 16;
18002
+ const index = (y2 - cy * 16) * 256 + (z2 - cz * 16) * 16 + x2 - cx * 16;
17435
18003
  return (section?.fluidByteIndex(index) ?? 0) | (section?.isOccludingIndex(index) ? 64 : 0);
18004
+ },
18005
+ (x2, y2, z2) => this.getBiomeAt(x2, y2, z2),
18006
+ async (biomes) => {
18007
+ if (this._chunks.get(key) !== chunk) return;
18008
+ const columnKey = `${x}_${z}`;
18009
+ let column = this.biomes.get(columnKey);
18010
+ if (biomes) {
18011
+ if (!column) this.biomes.set(columnKey, column = /* @__PURE__ */ new Map());
18012
+ column.set(y, biomes);
18013
+ } else {
18014
+ column?.delete(y);
18015
+ if (!column?.size) this.biomes.delete(columnKey);
18016
+ }
18017
+ await chunk.refreshBiomes();
18018
+ await this._chunks.get(`${x}_${y + 1}_${z}`)?.refreshBiomes();
17436
18019
  }
17437
18020
  );
17438
18021
  this._chunks.set(key, chunk);
@@ -17445,7 +18028,7 @@ var MineRenderWorld = class {
17445
18028
  }
17446
18029
  /** Converts block coordinates to scene units by multiplying each component by 16. */
17447
18030
  static worldToScenePosition(pos) {
17448
- return new import_three41.Vector3(
18031
+ return new import_three42.Vector3(
17449
18032
  pos.x * 16,
17450
18033
  pos.y * 16,
17451
18034
  pos.z * 16
@@ -17453,7 +18036,7 @@ var MineRenderWorld = class {
17453
18036
  }
17454
18037
  /** Converts scene units to block coordinates without rounding to an integer block. */
17455
18038
  static sceneToWorldPosition(pos) {
17456
- return new import_three41.Vector3(
18039
+ return new import_three42.Vector3(
17457
18040
  pos.x / 16,
17458
18041
  pos.y / 16,
17459
18042
  pos.z / 16
@@ -17476,7 +18059,7 @@ var MineRenderWorld = class {
17476
18059
  init_Model();
17477
18060
 
17478
18061
  // src/world/SectionMesh.ts
17479
- var import_three42 = require("three");
18062
+ var import_three43 = require("three");
17480
18063
  init_CanvasCompat();
17481
18064
  init_Materials();
17482
18065
 
@@ -17487,6 +18070,7 @@ function buildSectionGeometry(input) {
17487
18070
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
17488
18071
  if (count > indices.length || count > templates.length || count > cullMasks.length) throw new RangeError("Section entry count exceeds its arrays");
17489
18072
  if (fluids && fluids.cells.length !== 5832) throw new RangeError("Section fluid cells must cover an 18\xD718\xD718 neighborhood");
18073
+ if (fluids?.waterTints && fluids.waterTints.length !== 4096 * 3) throw new RangeError("Section water tints must contain RGB for 4096 cells");
17490
18074
  for (const template of templateData) {
17491
18075
  const { quads } = template;
17492
18076
  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) {
@@ -17564,7 +18148,9 @@ function buildSectionGeometry(input) {
17564
18148
  height
17565
18149
  });
17566
18150
  }
17567
- fluidPages.get(fluid).fluids.push({ quads, x, y, z, data });
18151
+ const tintIndex = (x + z * 16 + y * 256) * 3;
18152
+ const tint = fluid === "water" && fluids.waterTints ? fluids.waterTints.subarray(tintIndex, tintIndex + 3) : data.tint;
18153
+ fluidPages.get(fluid).fluids.push({ quads, x, y, z, data, tint });
17568
18154
  }
17569
18155
  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));
17570
18156
  }
@@ -17613,7 +18199,7 @@ function buildSectionGeometry(input) {
17613
18199
  for (let corner = 0; corner < 6; corner++) outputIndices[indexOffset++] = start + template.indices[quad * 6 + corner];
17614
18200
  }
17615
18201
  }
17616
- for (const { quads, x, y, z, data } of page.fluids) {
18202
+ for (const { quads, x, y, z, data, tint } of page.fluids) {
17617
18203
  const atlas = atlases[data.atlas];
17618
18204
  const placement = page.placements[0];
17619
18205
  const mapU = (u) => (placement.x + u * atlas.width) / page.width;
@@ -17637,7 +18223,7 @@ function buildSectionGeometry(input) {
17637
18223
  mapU((rect[0] + rect[2]) / atlas.width),
17638
18224
  mapV(1 - rect[1] / atlas.height)
17639
18225
  ], vertex * 4);
17640
- colors.set(data.tint, vertex * 3);
18226
+ colors.set(tint, vertex * 3);
17641
18227
  }
17642
18228
  outputIndices.set([start, start + 1, start + 2, start, start + 2, start + 3], indexOffset);
17643
18229
  indexOffset += 6;
@@ -17705,7 +18291,7 @@ var SectionWorker = class _SectionWorker {
17705
18291
 
17706
18292
  // src/world/SectionMesh.ts
17707
18293
  var templateCache = /* @__PURE__ */ new WeakMap();
17708
- var WATER_TINT = new import_three42.Color(4159204).toArray();
18294
+ var WATER_TINT = new import_three43.Color(4159204).toArray();
17709
18295
  function toInput(entries, maxAtlasSize, fluids) {
17710
18296
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
17711
18297
  const atlases = [];
@@ -17779,7 +18365,7 @@ function toInput(entries, maxAtlasSize, fluids) {
17779
18365
  input.cullMasks[i] = cullMask;
17780
18366
  }
17781
18367
  if (fluids) {
17782
- input.fluids = { cells: fluids.cells };
18368
+ input.fluids = { cells: fluids.cells, waterTints: fluids.waterTints };
17783
18369
  for (const kind of ["water", "lava"]) {
17784
18370
  const atlas = fluids[kind];
17785
18371
  if (!atlas) continue;
@@ -17800,7 +18386,7 @@ function toInput(entries, maxAtlasSize, fluids) {
17800
18386
  }
17801
18387
  return { input, atlases };
17802
18388
  }
17803
- var SectionMesh = class _SectionMesh extends import_three42.Group {
18389
+ var SectionMesh = class _SectionMesh extends import_three43.Group {
17804
18390
  constructor() {
17805
18391
  super(...arguments);
17806
18392
  this.ownedMeshes = [];
@@ -17825,26 +18411,26 @@ var SectionMesh = class _SectionMesh extends import_three42.Group {
17825
18411
  for (const { atlas, x, y } of page.placements) {
17826
18412
  context.drawImage(atlases[atlas].image.canvas, x, y);
17827
18413
  }
17828
- const geometry = new import_three42.BufferGeometry();
17829
- geometry.setAttribute("position", new import_three42.Float32BufferAttribute(page.positions.buffer, 3));
17830
- geometry.setAttribute("normal", new import_three42.Float32BufferAttribute(page.normals.buffer, 3));
17831
- geometry.setAttribute("uv", new import_three42.Float32BufferAttribute(page.uvs.buffer, 2));
17832
- geometry.setAttribute("uvBounds", new import_three42.Float32BufferAttribute(page.uvBounds.buffer, 4));
17833
- geometry.setAttribute("color", new import_three42.Float32BufferAttribute(page.colors.buffer, 3));
17834
- geometry.setIndex(new import_three42.Uint32BufferAttribute(page.indices.buffer, 1));
18414
+ const geometry = new import_three43.BufferGeometry();
18415
+ geometry.setAttribute("position", new import_three43.Float32BufferAttribute(page.positions.buffer, 3));
18416
+ geometry.setAttribute("normal", new import_three43.Float32BufferAttribute(page.normals.buffer, 3));
18417
+ geometry.setAttribute("uv", new import_three43.Float32BufferAttribute(page.uvs.buffer, 2));
18418
+ geometry.setAttribute("uvBounds", new import_three43.Float32BufferAttribute(page.uvBounds.buffer, 4));
18419
+ geometry.setAttribute("color", new import_three43.Float32BufferAttribute(page.colors.buffer, 3));
18420
+ geometry.setIndex(new import_three43.Uint32BufferAttribute(page.indices.buffer, 1));
17835
18421
  geometry.computeBoundingBox();
17836
18422
  geometry.computeBoundingSphere();
17837
18423
  const transparent = page.layer === 2 ? atlases[page.placements[0].atlas].hasTranslucency : page.layer === 1;
17838
18424
  const material = Materials.createShadedCanvasMaterial(canvas, transparent, false, true);
17839
- material.side = import_three42.FrontSide;
18425
+ material.side = import_three43.FrontSide;
17840
18426
  if (page.layer === 2) {
17841
- material.side = import_three42.DoubleSide;
18427
+ material.side = import_three43.DoubleSide;
17842
18428
  material.forceSinglePass = true;
17843
18429
  material.depthWrite = !material.transparent;
17844
18430
  }
17845
18431
  material.vertexColors = true;
17846
18432
  const texture = material.uniforms?.map?.value ?? material.map;
17847
- const mesh = new import_three42.Mesh(geometry, material);
18433
+ const mesh = new import_three43.Mesh(geometry, material);
17848
18434
  section.add(mesh);
17849
18435
  const unsubscribe = [];
17850
18436
  for (const { atlas: id, x, y } of page.placements) {
@@ -17880,17 +18466,20 @@ var SectionMesh = class _SectionMesh extends import_three42.Group {
17880
18466
  init_FluidGeometry();
17881
18467
  init_FluidQuads();
17882
18468
  init_CubeFace();
18469
+ var WATER_TINT2 = new import_three44.Color(4159204).toArray();
17883
18470
  var Chunk = class _Chunk {
17884
- constructor(scene, x, y, z, onBlocksChanged, sectionModels, neighborCell) {
18471
+ constructor(scene, x, y, z, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged) {
17885
18472
  this.onBlocksChanged = onBlocksChanged;
17886
18473
  this.sectionModels = sectionModels;
17887
18474
  this.neighborCell = neighborCell;
18475
+ this.biomeAt = biomeAt;
18476
+ this.onBiomesChanged = onBiomesChanged;
17888
18477
  this.data = new ChunkData();
17889
18478
  this.renderedBlocks = /* @__PURE__ */ new Map();
17890
18479
  this.sectionBlocks = /* @__PURE__ */ new Map();
17891
18480
  this.hiddenBlocks = /* @__PURE__ */ new Set();
17892
18481
  this.fluidCells = new Uint8Array(4096);
17893
- this.blockPosition = new import_three43.Vector3();
18482
+ this.blockPosition = new import_three44.Vector3();
17894
18483
  this.meshDirty = false;
17895
18484
  this.meshGeneration = 0;
17896
18485
  this.scene = scene;
@@ -17900,21 +18489,71 @@ var Chunk = class _Chunk {
17900
18489
  }
17901
18490
  getBlockAt(posOrX, y, z) {
17902
18491
  if (typeof posOrX == "number") {
17903
- return this.getBlockAt(new import_three43.Vector3(posOrX, y, z));
18492
+ return this.getBlockAt(new import_three44.Vector3(posOrX, y, z));
17904
18493
  }
17905
18494
  if (isTripleArray(posOrX)) {
17906
- return this.getBlockAt(new import_three43.Vector3(posOrX[0], posOrX[1], posOrX[2]));
18495
+ return this.getBlockAt(new import_three44.Vector3(posOrX[0], posOrX[1], posOrX[2]));
17907
18496
  }
17908
18497
  const pos = this.blockPosition.set(posOrX.x - this.x * 16, posOrX.y - this.y * 16, posOrX.z - this.z * 16);
17909
18498
  const index = _Chunk.chunkPosToBlockIndex(pos);
17910
18499
  return this.renderedBlocks.get(index);
17911
18500
  }
18501
+ getBiomeAt(posOrX, y, z) {
18502
+ if (typeof posOrX === "number") return this.getBiomeAt(new import_three44.Vector3(posOrX, y, z));
18503
+ if (isTripleArray(posOrX)) return this.getBiomeAt(new import_three44.Vector3(...posOrX));
18504
+ const pos = this.worldPosToChunkPos(posOrX);
18505
+ _Chunk.chunkPosToBlockIndex(pos);
18506
+ 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];
18507
+ }
18508
+ /** Copies 64 biome IDs in x + z * 4 + y * 16 order and refreshes placed blocks. Omit the array to clear biome data. */
18509
+ setBiomes(biomes) {
18510
+ const copy = biomes ? [...biomes] : void 0;
18511
+ if (copy && (copy.length !== 64 || !copy.every((id) => typeof id === "string" && id.length > 0))) {
18512
+ throw new RangeError("Section biomes must contain 64 nonempty biome IDs");
18513
+ }
18514
+ if (this.onBiomesChanged) return this.onBiomesChanged(copy);
18515
+ this.biomes = copy;
18516
+ return this.refreshBiomes();
18517
+ }
18518
+ /** Rebuilds biome-tinted blocks while preserving hidden blocks and unrelated render objects. */
18519
+ async refreshBiomes() {
18520
+ const affected = /* @__PURE__ */ new Map();
18521
+ const blocks = [];
18522
+ for (const index of this.renderedBlocks.keys()) {
18523
+ const block = this.data.get(index);
18524
+ const stateKey = ChunkData.paletteKey(block);
18525
+ let matches = affected.get(stateKey);
18526
+ if (!matches) {
18527
+ const key = AssetKey.parse("blockstates", block.type);
18528
+ matches = BlockStates.get(key).then(async (state) => {
18529
+ const origin = state?.key ?? key;
18530
+ const [source, fluid] = await Promise.all([BlockTints.biomeSource(origin), getBlockFluidState(origin, block.properties)]);
18531
+ return !!source || fluid?.kind === "water";
18532
+ });
18533
+ affected.set(stateKey, matches);
18534
+ }
18535
+ if (await matches) blocks.push({ index, block });
18536
+ }
18537
+ if (!blocks.length) return;
18538
+ const hidden = new Set(this.hiddenBlocks);
18539
+ try {
18540
+ await this.placeBlocks(blocks);
18541
+ } finally {
18542
+ for (const index of hidden) {
18543
+ this.hiddenBlocks.add(index);
18544
+ this.renderedBlocks.get(index)?.object?.setVisible(false);
18545
+ }
18546
+ const positions = blocks.map(({ index }) => new import_three44.Vector3(...this.positionAt(index)));
18547
+ if (this.onBlocksChanged) await this.onBlocksChanged(positions);
18548
+ else await this.rebuildSectionMesh();
18549
+ }
18550
+ }
17912
18551
  async setBlockAt(posOrX, yOrBlock, z, block) {
17913
18552
  if (typeof posOrX == "number") {
17914
- return this.setBlockAt(new import_three43.Vector3(posOrX, yOrBlock, z), block);
18553
+ return this.setBlockAt(new import_three44.Vector3(posOrX, yOrBlock, z), block);
17915
18554
  }
17916
18555
  if (isTripleArray(posOrX)) {
17917
- return this.setBlockAt(new import_three43.Vector3(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
18556
+ return this.setBlockAt(new import_three44.Vector3(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
17918
18557
  }
17919
18558
  const worldPos = posOrX;
17920
18559
  const pos = this.worldPosToChunkPos(worldPos);
@@ -17947,14 +18586,15 @@ var Chunk = class _Chunk {
17947
18586
  nbt: block.nbt === void 0 ? void 0 : structuredClone(block.nbt)
17948
18587
  } : void 0
17949
18588
  }));
17950
- const states = new Map(placements.filter(({ key }) => key !== void 0).map(({ key, block }) => [key, block]));
17951
- const settled = await Promise.allSettled([...states.values()].map(async (stored) => {
17952
- const blockState3 = await BlockStates.get(AssetKey.parse("blockstates", stored.type));
17953
- const perBlock = !blockState3 || !this.sectionModels || !!(blockState3.key && BlockEntities.entry(await BlockEntities.getIndex(blockState3.key.root), blockState3.key.toNamespacedString()));
17954
- const fluid = await getBlockFluidState(blockState3?.key, stored.properties);
17955
- const prepared = perBlock || fluid?.renderModel === false ? void 0 : await this.sectionModels.prepareState(blockState3, stored.properties);
17956
- return { blockState: blockState3, perBlock, fluid, prepared, stored };
17957
- }));
18589
+ const states = /* @__PURE__ */ new Map();
18590
+ for (const { index, key, block } of placements) {
18591
+ if (key === void 0) continue;
18592
+ let state = states.get(key);
18593
+ if (!state) states.set(key, state = { stored: block, indices: [] });
18594
+ state.indices.push(index);
18595
+ }
18596
+ const biomes = /* @__PURE__ */ new Map();
18597
+ const settled = await Promise.allSettled([...states.values()].map(({ stored, indices }) => this.resolveState(stored, indices, biomes)));
17958
18598
  const resolutions = new Map([...states.keys()].map((key, i) => [key, settled[i]]));
17959
18599
  const positions = [];
17960
18600
  let failed = false, failure;
@@ -17967,7 +18607,7 @@ var Chunk = class _Chunk {
17967
18607
  if (this.fluidCells[index]) this.meshDirty = true;
17968
18608
  this.fluidCells[index] = 0;
17969
18609
  this.renderedBlocks.delete(index);
17970
- const worldPos = new import_three43.Vector3(
18610
+ const worldPos = new import_three44.Vector3(
17971
18611
  this.x * 16 + index % 16,
17972
18612
  this.y * 16 + Math.floor(index / 256),
17973
18613
  this.z * 16 + Math.floor(index / 16) % 16
@@ -17978,7 +18618,8 @@ var Chunk = class _Chunk {
17978
18618
  if (!readBlock) continue;
17979
18619
  const resolved = resolutions.get(key);
17980
18620
  if (resolved.status === "rejected") throw resolved.reason;
17981
- const { blockState: blockState3, perBlock, fluid, prepared, stored } = resolved.value;
18621
+ const { blockState: blockState3, perBlock, fluid, prepared, contexts, stored } = resolved.value;
18622
+ const context = contexts.get(index);
17982
18623
  if (!blockState3) {
17983
18624
  this.data.assign(index, 0);
17984
18625
  continue;
@@ -17986,9 +18627,10 @@ var Chunk = class _Chunk {
17986
18627
  const variantPosition = worldPos.toArray();
17987
18628
  if (fluid) {
17988
18629
  this.fluidCells[index] = (fluid.kind === "water" ? 16 : 32) | Math.min(fluid.level, 8);
18630
+ if (fluid.kind === "water") this.setWaterTint(index, context?.waterTint);
17989
18631
  this.meshDirty = true;
17990
18632
  }
17991
- const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : prepared?.pick(variantPosition);
18633
+ const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : (context?.prepared ?? prepared)?.pick(variantPosition);
17992
18634
  if (templates) {
17993
18635
  this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0 })));
17994
18636
  this.meshDirty = true;
@@ -17998,7 +18640,9 @@ var Chunk = class _Chunk {
17998
18640
  instanceMeshes: true,
17999
18641
  maxInstanceCount: 2e3,
18000
18642
  initialState: stored.properties,
18001
- variantPosition
18643
+ variantPosition,
18644
+ biome: context?.biome,
18645
+ biomePosition: this.biomePosition(index, blockState3, stored)
18002
18646
  });
18003
18647
  object4.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
18004
18648
  }
@@ -18021,24 +18665,21 @@ var Chunk = class _Chunk {
18021
18665
  for (const info of this.renderedBlocks.values()) info.object?.removeFromScene();
18022
18666
  this.sectionBlocks.clear();
18023
18667
  this.fluidCells.fill(0);
18668
+ this.waterTints = void 0;
18024
18669
  this.renderedBlocks.clear();
18025
18670
  this.hiddenBlocks.clear();
18026
18671
  this.meshDirty = true;
18027
18672
  this.data.copyFrom(data);
18028
- const ids = /* @__PURE__ */ new Set();
18673
+ const ids = /* @__PURE__ */ new Map();
18029
18674
  for (let index = 0; index < 4096; index++) {
18030
18675
  const id = this.data.idAt(index);
18031
- if (id) ids.add(id);
18032
- }
18033
- const palette = [...ids];
18034
- const settled = await Promise.allSettled(palette.map(async (id) => {
18035
- const state = this.data.stateAt(id);
18036
- const blockState3 = await BlockStates.get(AssetKey.parse("blockstates", state.type));
18037
- const perBlock = !blockState3 || !this.sectionModels || !!(blockState3.key && BlockEntities.entry(await BlockEntities.getIndex(blockState3.key.root), blockState3.key.toNamespacedString()));
18038
- const fluid = await getBlockFluidState(blockState3?.key, state.properties);
18039
- const prepared = perBlock || fluid?.renderModel === false ? void 0 : await this.sectionModels.prepareState(blockState3, state.properties);
18040
- return { blockState: blockState3, perBlock, fluid, prepared };
18041
- }));
18676
+ if (!id) continue;
18677
+ let indices = ids.get(id);
18678
+ if (!indices) ids.set(id, indices = []);
18679
+ indices.push(index);
18680
+ }
18681
+ const palette = [...ids.keys()], biomes = /* @__PURE__ */ new Map();
18682
+ const settled = await Promise.allSettled(palette.map((id) => this.resolveState(this.data.stateAt(id), ids.get(id), biomes)));
18042
18683
  const resolutions = new Map(palette.map((id, i) => [id, settled[i]]));
18043
18684
  const perBlocks = [];
18044
18685
  let failed = false, failure;
@@ -18048,24 +18689,28 @@ var Chunk = class _Chunk {
18048
18689
  try {
18049
18690
  const resolved = resolutions.get(id);
18050
18691
  if (resolved.status === "rejected") throw resolved.reason;
18051
- const { blockState: blockState3, perBlock, fluid, prepared } = resolved.value;
18692
+ const { blockState: blockState3, perBlock, fluid, prepared, contexts } = resolved.value;
18693
+ const context = contexts.get(index);
18052
18694
  if (!blockState3) {
18053
18695
  this.data.assign(index, 0);
18054
18696
  this.fluidCells[index] = 0;
18055
18697
  continue;
18056
18698
  }
18057
- if (fluid) this.fluidCells[index] = (fluid.kind === "water" ? 16 : 32) | Math.min(fluid.level, 8);
18699
+ if (fluid) {
18700
+ this.fluidCells[index] = (fluid.kind === "water" ? 16 : 32) | Math.min(fluid.level, 8);
18701
+ if (fluid.kind === "water") this.setWaterTint(index, context?.waterTint);
18702
+ }
18058
18703
  const variantPosition = [
18059
18704
  this.x * 16 + index % 16,
18060
18705
  this.y * 16 + Math.floor(index / 256),
18061
18706
  this.z * 16 + Math.floor(index / 16) % 16
18062
18707
  ];
18063
- const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : prepared?.pick(variantPosition);
18708
+ const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : (context?.prepared ?? prepared)?.pick(variantPosition);
18064
18709
  if (templates) {
18065
18710
  this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0 })));
18066
18711
  this.meshDirty = true;
18067
18712
  } else {
18068
- perBlocks.push({ index, blockState: blockState3 });
18713
+ perBlocks.push({ index, blockState: blockState3, biome: context?.biome });
18069
18714
  }
18070
18715
  const readBlock = this.data.snapshot(index);
18071
18716
  this.renderedBlocks.set(index, { get block() {
@@ -18078,7 +18723,7 @@ var Chunk = class _Chunk {
18078
18723
  failed = true;
18079
18724
  }
18080
18725
  }
18081
- for (const { index, blockState: blockState3 } of perBlocks) {
18726
+ for (const { index, blockState: blockState3, biome } of perBlocks) {
18082
18727
  let object4;
18083
18728
  try {
18084
18729
  object4 = await this.scene.addBlock(blockState3, {
@@ -18086,13 +18731,11 @@ var Chunk = class _Chunk {
18086
18731
  instanceMeshes: true,
18087
18732
  maxInstanceCount: 2e3,
18088
18733
  initialState: this.data.stateAt(this.data.idAt(index)).properties,
18089
- variantPosition: [
18090
- this.x * 16 + index % 16,
18091
- this.y * 16 + Math.floor(index / 256),
18092
- this.z * 16 + Math.floor(index / 16) % 16
18093
- ]
18734
+ variantPosition: this.positionAt(index),
18735
+ biome,
18736
+ biomePosition: this.biomePosition(index, blockState3, this.data.stateAt(this.data.idAt(index)))
18094
18737
  });
18095
- object4.setPosition(new import_three43.Vector3(
18738
+ object4.setPosition(new import_three44.Vector3(
18096
18739
  this.x * 256 + index % 16 * 16,
18097
18740
  this.y * 256 + Math.floor(index / 256) * 16,
18098
18741
  this.z * 256 + Math.floor(index / 16) % 16 * 16
@@ -18109,6 +18752,68 @@ var Chunk = class _Chunk {
18109
18752
  }
18110
18753
  if (failed) throw failure;
18111
18754
  }
18755
+ positionAt(index) {
18756
+ return [this.x * 16 + index % 16, this.y * 16 + Math.floor(index / 256), this.z * 16 + Math.floor(index / 16) % 16];
18757
+ }
18758
+ biomePosition(index, blockState3, stored) {
18759
+ const position = this.positionAt(index), name = blockState3.key?.toNamespacedString();
18760
+ if (stored.properties?.half === "upper" && (name === "minecraft:tall_grass" || name === "minecraft:large_fern")) position[1]--;
18761
+ return position;
18762
+ }
18763
+ async resolveState(stored, indices, biomes) {
18764
+ const blockState3 = await BlockStates.get(AssetKey.parse("blockstates", stored.type));
18765
+ const perBlock = !blockState3 || !this.sectionModels || !!(blockState3.key && BlockEntities.entry(await BlockEntities.getIndex(blockState3.key.root), blockState3.key.toNamespacedString()));
18766
+ const fluid = await getBlockFluidState(blockState3?.key, stored.properties);
18767
+ 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;
18768
+ const source = hasBiomes && blockState3 ? await BlockTints.biomeSource(blockState3.key) : void 0;
18769
+ const prepare = (color2) => perBlock || fluid?.renderModel === false ? void 0 : this.sectionModels.prepareState(blockState3, stored.properties, color2);
18770
+ const contexts = /* @__PURE__ */ new Map();
18771
+ const groups = /* @__PURE__ */ new Map();
18772
+ if (blockState3 && (source || fluid?.kind === "water")) {
18773
+ for (const index of indices) {
18774
+ const position = this.biomePosition(index, blockState3, stored);
18775
+ const biome = this.biomeAt ? this.biomeAt(...position) : position[1] >= this.y * 16 ? this.getBiomeAt(position) : void 0;
18776
+ if (!biome) continue;
18777
+ const key = JSON.stringify(source === "grass" ? [biome, position[0], position[2]] : [biome]);
18778
+ let group = groups.get(key);
18779
+ if (!group) groups.set(key, group = { biome, position, indices: [] });
18780
+ group.indices.push(index);
18781
+ }
18782
+ }
18783
+ const groupedCount = [...groups.values()].reduce((sum, group) => sum + group.indices.length, 0);
18784
+ const prepared = groupedCount < indices.length ? await prepare() : void 0;
18785
+ await Promise.all([...groups.values()].map(async ({ biome, position, indices: indices2 }) => {
18786
+ const available = () => {
18787
+ const key = JSON.stringify([biome, blockState3?.key?.root]);
18788
+ let found = biomes.get(key);
18789
+ if (!found) {
18790
+ found = Biomes.get(biome, blockState3?.key).then((value) => value !== void 0);
18791
+ biomes.set(key, found);
18792
+ }
18793
+ return found;
18794
+ };
18795
+ const context = { biome };
18796
+ if (perBlock) {
18797
+ if (!await available()) context.biome = void 0;
18798
+ } else {
18799
+ context.prepared = await prepare(async () => await available() ? BlockTints.getBiomeColor(blockState3?.key, biome, position) : void 0);
18800
+ if (fluid?.kind === "water" && await available()) {
18801
+ const color2 = await Biomes.getColor(biome, "water", blockState3?.key);
18802
+ if (color2 !== void 0 && color2 !== 4159204) context.waterTint = new import_three44.Color(color2).toArray();
18803
+ }
18804
+ }
18805
+ for (const index of indices2) contexts.set(index, context);
18806
+ }));
18807
+ return { blockState: blockState3, perBlock, fluid, prepared, contexts, stored };
18808
+ }
18809
+ setWaterTint(index, tint) {
18810
+ if (!this.sectionModels || !this.waterTints && tint === void 0) return;
18811
+ if (!this.waterTints) {
18812
+ this.waterTints = new Float32Array(4096 * 3);
18813
+ for (let cell = 0; cell < 4096; cell++) this.waterTints.set(WATER_TINT2, cell * 3);
18814
+ }
18815
+ this.waterTints.set(tint ?? WATER_TINT2, index * 3);
18816
+ }
18112
18817
  /** Changes visibility at world block coordinates while preserving the block's data. */
18113
18818
  async setBlockVisibleAt(pos, visible) {
18114
18819
  const index = _Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos));
@@ -18212,7 +18917,7 @@ var Chunk = class _Chunk {
18212
18917
  if (block && !block.object) origins.set(kind, block.block.type);
18213
18918
  }
18214
18919
  if (origins.size) {
18215
- fluids = { cells: new Uint8Array(18 * 18 * 18) };
18920
+ fluids = { cells: new Uint8Array(18 * 18 * 18), waterTints: this.waterTints?.slice() };
18216
18921
  for (let y = -1; y <= 16; y++) {
18217
18922
  for (let z = -1; z <= 16; z++) {
18218
18923
  for (let x = -1; x <= 16; x++) {
@@ -18243,7 +18948,7 @@ var Chunk = class _Chunk {
18243
18948
  /** Removes stored blocks and render objects. Set `notify` to false to skip the owning world's update. */
18244
18949
  async clear(notify = true) {
18245
18950
  const positions = notify && this.onBlocksChanged ? [...this.renderedBlocks.keys()].map((index) => this.chunkPosToWorldPos(
18246
- new import_three43.Vector3(index % 16, Math.floor(index / 256), Math.floor(index / 16) % 16)
18951
+ new import_three44.Vector3(index % 16, Math.floor(index / 256), Math.floor(index / 16) % 16)
18247
18952
  )) : [];
18248
18953
  for (const info of this.renderedBlocks.values()) info.object?.removeFromScene();
18249
18954
  this.sectionBlocks.clear();
@@ -18254,8 +18959,13 @@ var Chunk = class _Chunk {
18254
18959
  this.renderedBlocks.clear();
18255
18960
  this.hiddenBlocks.clear();
18256
18961
  this.fluidCells.fill(0);
18962
+ this.waterTints = void 0;
18257
18963
  this.data.clear();
18258
- if (notify) await this.onBlocksChanged?.(positions);
18964
+ this.biomes = void 0;
18965
+ if (notify) {
18966
+ await this.onBiomesChanged?.(void 0);
18967
+ await this.onBlocksChanged?.(positions);
18968
+ }
18259
18969
  }
18260
18970
  async dispose() {
18261
18971
  await this.clear();
@@ -18271,14 +18981,14 @@ var Chunk = class _Chunk {
18271
18981
  return pos.y * 16 * 16 + pos.z * 16 + pos.x;
18272
18982
  }
18273
18983
  worldPosToChunkPos(pos) {
18274
- return new import_three43.Vector3(
18984
+ return new import_three44.Vector3(
18275
18985
  pos.x - this.x * 16,
18276
18986
  pos.y - this.y * 16,
18277
18987
  pos.z - this.z * 16
18278
18988
  );
18279
18989
  }
18280
18990
  chunkPosToWorldPos(pos) {
18281
- return new import_three43.Vector3(
18991
+ return new import_three44.Vector3(
18282
18992
  this.x * 16 + pos.x,
18283
18993
  this.y * 16 + pos.y,
18284
18994
  this.z * 16 + pos.z
@@ -18641,6 +19351,7 @@ var NodeRenderer = class _NodeRenderer extends Renderer {
18641
19351
  BannerPatterns,
18642
19352
  BasicAssetKey,
18643
19353
  BatchedExecutor,
19354
+ Biomes,
18644
19355
  BlockEntities,
18645
19356
  BlockObject,
18646
19357
  BlockStateResolver,
@@ -18672,6 +19383,7 @@ var NodeRenderer = class _NodeRenderer extends Renderer {
18672
19383
  EntityObject,
18673
19384
  Env,
18674
19385
  FluidModelObject,
19386
+ FlyControls,
18675
19387
  Fonts,
18676
19388
  GUI_CONTAINER_LAYOUTS,
18677
19389
  Geometries,
@@ -18777,6 +19489,7 @@ var NodeRenderer = class _NodeRenderer extends Renderer {
18777
19489
  isDisposable,
18778
19490
  isDoubleArray,
18779
19491
  isEntityObject,
19492
+ isFlyControls,
18780
19493
  isGuiObject,
18781
19494
  isInstanceManager,
18782
19495
  isInstanceReference,