minerender 2.1.0-alpha.41 → 2.1.0-alpha.42

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.
@@ -32,8 +32,8 @@ var require_dist = __commonJS({
32
32
  __assign = Object.assign || function(t) {
33
33
  for (var s, i = 1, n = arguments.length; i < n; i++) {
34
34
  s = arguments[i];
35
- for (var p11 in s) if (Object.prototype.hasOwnProperty.call(s, p11))
36
- t[p11] = s[p11];
35
+ for (var p10 in s) if (Object.prototype.hasOwnProperty.call(s, p10))
36
+ t[p10] = s[p10];
37
37
  }
38
38
  return t;
39
39
  };
@@ -770,11 +770,11 @@ var ImageLoader = class _ImageLoader {
770
770
  static async loadCanvasData(src) {
771
771
  const keyStr = serializeImageKey({ src });
772
772
  const info = this.getInfo(src);
773
- const cached = Caching.rawImageCache.getIfPresent(keyStr);
773
+ const cached2 = Caching.rawImageCache.getIfPresent(keyStr);
774
774
  try {
775
775
  return await this.infoToCanvasData(await info);
776
776
  } catch (err) {
777
- if (cached && Caching.rawImageCache.getIfPresent(keyStr) === cached) {
777
+ if (cached2 && Caching.rawImageCache.getIfPresent(keyStr) === cached2) {
778
778
  Caching.rawImageCache.invalidate(keyStr);
779
779
  }
780
780
  throw err;
@@ -911,8 +911,8 @@ var Textures = class _Textures {
911
911
  }
912
912
  static getImage(key) {
913
913
  const keyStr = serializeTextureKey(key);
914
- const cached = Caching.textureCache.peek(keyStr);
915
- if (cached && TextureLoader.hasFailed(cached)) {
914
+ const cached2 = Caching.textureCache.peek(keyStr);
915
+ if (cached2 && TextureLoader.hasFailed(cached2)) {
916
916
  Caching.textureCache.invalidate(keyStr);
917
917
  }
918
918
  return Caching.textureCache.get(keyStr, (k) => {
@@ -1180,12 +1180,12 @@ var AssetKey = class _AssetKey extends BasicAssetKey {
1180
1180
  return a.join("/");
1181
1181
  }
1182
1182
  getFullPath() {
1183
- let p11 = [];
1183
+ let p10 = [];
1184
1184
  if (this.type) {
1185
- p11.push(this.type);
1185
+ p10.push(this.type);
1186
1186
  }
1187
- p11.push(this.path);
1188
- return p11.join("/");
1187
+ p10.push(this.path);
1188
+ return p10.join("/");
1189
1189
  }
1190
1190
  toNamespacedString() {
1191
1191
  return this.namespace + ":" + this.getFullPath();
@@ -1272,7 +1272,7 @@ var ModelMerger = class {
1272
1272
  merged.display = { ...inheritedDisplay, ...DisplayTransforms.withHandFallbacks(source.display) };
1273
1273
  }
1274
1274
  }
1275
- merged.hierarchy = models.map((m) => m.parent).filter((p11) => `${p11}`);
1275
+ merged.hierarchy = models.map((m) => m.parent).filter((p10) => `${p10}`);
1276
1276
  merged.hierarchy.push(`${merged.parent}`);
1277
1277
  return merged;
1278
1278
  }
@@ -2001,7 +2001,7 @@ var ModelTextures = class {
2001
2001
  static async get(key) {
2002
2002
  const keyStr = key.serialize();
2003
2003
  const pending = this.preload(key);
2004
- const cached = Caching.textureAssetCache.getIfPresent(keyStr);
2004
+ const cached2 = Caching.textureAssetCache.getIfPresent(keyStr);
2005
2005
  try {
2006
2006
  const asset = await pending;
2007
2007
  if (asset) {
@@ -2009,7 +2009,7 @@ var ModelTextures = class {
2009
2009
  }
2010
2010
  return void 0;
2011
2011
  } catch (err) {
2012
- if (cached && Caching.textureAssetCache.getIfPresent(keyStr) === cached) {
2012
+ if (cached2 && Caching.textureAssetCache.getIfPresent(keyStr) === cached2) {
2013
2013
  Caching.textureAssetCache.invalidate(keyStr);
2014
2014
  }
2015
2015
  throw err;
@@ -3902,6 +3902,93 @@ var ModelCulling = class {
3902
3902
  }
3903
3903
  };
3904
3904
 
3905
+ // src/model/block/BlockStateResolver.ts
3906
+ import { Euler as Euler5 } from "three";
3907
+ function matchesCondition(condition, state) {
3908
+ return Object.entries(condition).every(([key, value]) => {
3909
+ if (Array.isArray(value)) {
3910
+ if (key === "OR") return value.some((child) => matchesCondition(child, state));
3911
+ if (key === "AND") return value.every((child) => matchesCondition(child, state));
3912
+ return false;
3913
+ }
3914
+ return typeof value === "string" && state[key] !== void 0 && value.split("|").includes(`${state[key]}`);
3915
+ });
3916
+ }
3917
+ var BlockStateResolver = class {
3918
+ static async defaults(blockState) {
3919
+ const defaults = blockState.key ? await BlockStates.getDefaultState(blockState.key) : void 0;
3920
+ const state = {};
3921
+ if (defaults && Object.keys(defaults).length > 0) {
3922
+ for (const key in defaults) state[key] = defaults[key].default;
3923
+ } else if (blockState.variants) {
3924
+ const variant = Object.keys(blockState.variants)[0];
3925
+ if (variant) {
3926
+ for (const property of variant.split(",")) {
3927
+ const [key, value] = property.split("=");
3928
+ state[key] = value;
3929
+ }
3930
+ }
3931
+ } else if (blockState.multipart) {
3932
+ const condition = blockState.multipart.map((part) => part.when).find((when) => when !== void 0 && Object.values(when).every((value) => typeof value === "string"));
3933
+ for (const [key, value] of Object.entries(condition ?? {})) state[key] = value.split("|")[0];
3934
+ }
3935
+ return state;
3936
+ }
3937
+ static select(blockState, state, choose = this.choose) {
3938
+ return this.matching(blockState, state).map(choose);
3939
+ }
3940
+ static matching(blockState, state) {
3941
+ const out = [];
3942
+ if (blockState.variants) {
3943
+ if (Object.keys(blockState.variants).length === 1 && "" in blockState.variants) {
3944
+ out.push(blockState.variants[""]);
3945
+ } else {
3946
+ for (const variantKey in blockState.variants) {
3947
+ const matches = variantKey.split(",").every((property) => {
3948
+ const [key, value] = property.split("=");
3949
+ return `${state[key]}` === `${value}`;
3950
+ });
3951
+ if (matches) out.push(blockState.variants[variantKey]);
3952
+ }
3953
+ }
3954
+ } else if (blockState.multipart) {
3955
+ for (const part of blockState.multipart) {
3956
+ if (part.apply && (!part.when || matchesCondition(part.when, state))) {
3957
+ out.push(part.apply);
3958
+ }
3959
+ }
3960
+ }
3961
+ return out;
3962
+ }
3963
+ static choose(variants) {
3964
+ if (!Array.isArray(variants)) return variants;
3965
+ if (!variants.length) throw new MineRenderError("Blockstate variant arrays must not be empty");
3966
+ const total = variants.reduce((sum, variant) => {
3967
+ const weight = variant.weight ?? 1;
3968
+ if (!Number.isInteger(weight) || weight < 1) {
3969
+ throw new MineRenderError(`Invalid blockstate variant weight: ${weight}`);
3970
+ }
3971
+ return sum + weight;
3972
+ }, 0);
3973
+ let choice = Math.random() * total;
3974
+ for (const variant of variants) {
3975
+ choice -= variant.weight ?? 1;
3976
+ if (choice < 0) return variant;
3977
+ }
3978
+ return variants[variants.length - 1];
3979
+ }
3980
+ static rotation(variant) {
3981
+ const rotation = new Euler5();
3982
+ if (typeof variant.x !== "undefined") {
3983
+ rotation.x = toRadians(clampRotationDegrees(variant.x));
3984
+ }
3985
+ if (typeof variant.y !== "undefined") {
3986
+ rotation.y = toRadians(clampRotationDegrees(typeof variant.x !== "undefined" ? variant.y : 360 - variant.y));
3987
+ }
3988
+ return rotation;
3989
+ }
3990
+ };
3991
+
3905
3992
  // src/model/block/blockTints.json
3906
3993
  var blockTints_default = {
3907
3994
  "minecraft:large_fern": { source: "grass" },
@@ -3988,7 +4075,7 @@ var BlockTints = class {
3988
4075
  };
3989
4076
 
3990
4077
  // src/model/scene/ModelObject.ts
3991
- import { BoxGeometry as BoxGeometry5, BufferAttribute as BufferAttribute4, Color as Color3, Euler as Euler5, Matrix4 as Matrix44 } from "three";
4078
+ import { BoxGeometry as BoxGeometry5, BufferAttribute as BufferAttribute4, Color as Color3, Euler as Euler6, Matrix4 as Matrix44 } from "three";
3992
4079
 
3993
4080
  // src/three/BufferGeometryUtils.js
3994
4081
  import {
@@ -4582,7 +4669,7 @@ var ModelObject = class _ModelObject extends SceneObject {
4582
4669
  elGeo.clearGroups();
4583
4670
  }
4584
4671
  if (this.options.uvLockRotation) {
4585
- UVMapper.lockUvs(elGeo, el.faces, this.atlas, new Euler5(...this.options.uvLockRotation));
4672
+ UVMapper.lockUvs(elGeo, el.faces, this.atlas, new Euler6(...this.options.uvLockRotation));
4586
4673
  }
4587
4674
  if (this.options.tints) {
4588
4675
  const colors = new Float32Array(elGeo.getAttribute("position").count * 3).fill(1);
@@ -4678,18 +4765,7 @@ function isModelObject(obj) {
4678
4765
 
4679
4766
  // src/model/block/scene/BlockObject.ts
4680
4767
  var import_ts_deepmerge6 = __toESM(require_dist());
4681
- import { Euler as Euler6, Matrix4 as Matrix45, Quaternion as Quaternion4, Vector3 as Vector38 } from "three";
4682
- var p10 = prefix("BlockObject");
4683
- function matchesCondition(condition, state) {
4684
- return Object.entries(condition).every(([key, value]) => {
4685
- if (Array.isArray(value)) {
4686
- if (key === "OR") return value.some((child) => matchesCondition(child, state));
4687
- if (key === "AND") return value.every((child) => matchesCondition(child, state));
4688
- return false;
4689
- }
4690
- return typeof value === "string" && state[key] !== void 0 && value.split("|").includes(`${state[key]}`);
4691
- });
4692
- }
4768
+ import { Euler as Euler7, Matrix4 as Matrix45, Quaternion as Quaternion4, Vector3 as Vector38 } from "three";
4693
4769
  var BlockObject = class _BlockObject extends SceneObject {
4694
4770
  constructor(blockState, options) {
4695
4771
  super(options);
@@ -4713,22 +4789,7 @@ var BlockObject = class _BlockObject extends SceneObject {
4713
4789
  }
4714
4790
  async init() {
4715
4791
  if (this.options.applyDefaultState) {
4716
- const defaultState = this.blockState.key ? await BlockStates.getDefaultState(this.blockState.key) : void 0;
4717
- if (defaultState && Object.keys(defaultState).length > 0) {
4718
- const state = {};
4719
- for (let k in defaultState) {
4720
- state[k] = defaultState[k].default;
4721
- }
4722
- this._setState(state);
4723
- } else {
4724
- if (this.blockState.variants) {
4725
- this._setState(Object.keys(this.blockState.variants)[0]);
4726
- } else if (this.blockState.multipart) {
4727
- const condition = this.blockState.multipart.map((part) => part.when).find((when) => when !== void 0 && Object.values(when).every((value) => typeof value === "string"));
4728
- const state = Object.fromEntries(Object.entries(condition ?? {}).map(([key, value]) => [key, value.split("|")[0]]));
4729
- this._setState(state);
4730
- }
4731
- }
4792
+ this._setState(await BlockStateResolver.defaults(this.blockState));
4732
4793
  }
4733
4794
  if (this.options.initialState !== void 0) this._setState(this.options.initialState);
4734
4795
  await this.recreateModels();
@@ -4771,7 +4832,7 @@ var BlockObject = class _BlockObject extends SceneObject {
4771
4832
  const scale = new Vector38();
4772
4833
  this.getModelMatrix(model).decompose(position, rotation, scale);
4773
4834
  if (position.distanceToSquared(this.position) > 1e-10 || scale.distanceToSquared(new Vector38(1, 1, 1)) > 1e-10) return 0;
4774
- return ModelCulling.toLocalMask(worldMask, new Euler6().setFromQuaternion(rotation));
4835
+ return ModelCulling.toLocalMask(worldMask, new Euler7().setFromQuaternion(rotation));
4775
4836
  }
4776
4837
  async setCullMask(worldMask) {
4777
4838
  worldMask &= 63;
@@ -4819,39 +4880,7 @@ var BlockObject = class _BlockObject extends SceneObject {
4819
4880
  return ref;
4820
4881
  }
4821
4882
  async mapStateToVariant(state) {
4822
- const out = [];
4823
- if (this.blockState.variants) {
4824
- if (Object.keys(this.blockState.variants).length === 1 && "" in this.blockState.variants) {
4825
- out.push(this.getSingleVariant(this.blockState.variants[""]));
4826
- } else {
4827
- for (let variantKey in this.blockState.variants) {
4828
- const split = variantKey.split(",");
4829
- let matches = true;
4830
- for (let s of split) {
4831
- const [k, v] = s.split("=");
4832
- if (`${state[k]}` !== `${v}`) {
4833
- matches = false;
4834
- break;
4835
- }
4836
- }
4837
- if (matches) {
4838
- const variants = this.blockState.variants[variantKey];
4839
- out.push(this.getSingleVariant(variants));
4840
- }
4841
- }
4842
- }
4843
- } else if (this.blockState.multipart) {
4844
- for (let part of this.blockState.multipart) {
4845
- if (!part.apply) {
4846
- console.debug(p10, "Missing apply for blockState part", part);
4847
- continue;
4848
- }
4849
- if (!part.when || matchesCondition(part.when, state)) {
4850
- out.push(this.getSingleVariant(part.apply));
4851
- }
4852
- }
4853
- }
4854
- return out;
4883
+ return BlockStateResolver.select(this.blockState, state, (variants) => this.getSingleVariant(variants));
4855
4884
  }
4856
4885
  async recreateModels() {
4857
4886
  this.clearModels();
@@ -4862,21 +4891,7 @@ var BlockObject = class _BlockObject extends SceneObject {
4862
4891
  }
4863
4892
  }
4864
4893
  getSingleVariant(variants) {
4865
- if (!Array.isArray(variants)) return variants;
4866
- if (!variants.length) throw new MineRenderError("Blockstate variant arrays must not be empty");
4867
- const total = variants.reduce((sum, variant) => {
4868
- const weight = variant.weight ?? 1;
4869
- if (!Number.isInteger(weight) || weight < 1) {
4870
- throw new MineRenderError(`Invalid blockstate variant weight: ${weight}`);
4871
- }
4872
- return sum + weight;
4873
- }, 0);
4874
- let choice = Math.random() * total;
4875
- for (const variant of variants) {
4876
- choice -= variant.weight ?? 1;
4877
- if (choice < 0) return variant;
4878
- }
4879
- return variants[variants.length - 1];
4894
+ return BlockStateResolver.choose(variants);
4880
4895
  }
4881
4896
  // @deprecated
4882
4897
  async createAVariant(variants, instanceInfo) {
@@ -4884,13 +4899,7 @@ var BlockObject = class _BlockObject extends SceneObject {
4884
4899
  await this.createVariant(variant);
4885
4900
  }
4886
4901
  async createVariant(variant) {
4887
- const rotation = new Euler6();
4888
- if (typeof variant.x !== "undefined") {
4889
- rotation.x = toRadians(clampRotationDegrees(variant.x));
4890
- }
4891
- if (typeof variant.y !== "undefined") {
4892
- rotation.y = toRadians(clampRotationDegrees(typeof variant.x !== "undefined" ? variant.y : 360 - variant.y));
4893
- }
4902
+ const rotation = BlockStateResolver.rotation(variant);
4894
4903
  const modelKey = AssetKey.parse("models", variant.model);
4895
4904
  modelKey.root = this.blockState.key?.root;
4896
4905
  const model = await Models.getMerged(modelKey);
@@ -9393,32 +9402,121 @@ var AnvilParser = class {
9393
9402
  };
9394
9403
 
9395
9404
  // src/world/Chunk.ts
9405
+ import { Vector3 as Vector313 } from "three";
9406
+
9407
+ // src/world/MineRenderWorld.ts
9396
9408
  import { Vector3 as Vector312 } from "three";
9397
9409
 
9410
+ // src/world/SectionModels.ts
9411
+ import { Matrix4 as Matrix46, Vector3 as Vector311 } from "three";
9412
+ var SectionModel = class extends ModelObject {
9413
+ applyTextures() {
9414
+ }
9415
+ };
9416
+ function cached(map, key, load) {
9417
+ let value = map.get(key);
9418
+ if (!value) {
9419
+ value = load().catch((error) => {
9420
+ map.delete(key);
9421
+ throw error;
9422
+ });
9423
+ map.set(key, value);
9424
+ }
9425
+ return value;
9426
+ }
9427
+ var SectionModels = class {
9428
+ constructor(maxAtlasSize = 2048) {
9429
+ this.maxAtlasSize = maxAtlasSize;
9430
+ this.states = /* @__PURE__ */ new WeakMap();
9431
+ this.models = /* @__PURE__ */ new WeakMap();
9432
+ this.templates = /* @__PURE__ */ new Set();
9433
+ if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("maxAtlasSize must be a positive integer");
9434
+ }
9435
+ async get(blockState, properties = {}) {
9436
+ if (!blockState.variants || blockState.multipart) return void 0;
9437
+ let states = this.states.get(blockState);
9438
+ if (!states) this.states.set(blockState, states = /* @__PURE__ */ new Map());
9439
+ const key = JSON.stringify(Object.entries(properties).sort(([a], [b]) => a.localeCompare(b)));
9440
+ const prepared = await cached(states, key, () => this.prepare(blockState, properties));
9441
+ if (!prepared) return void 0;
9442
+ return prepared.templates.get(BlockStateResolver.choose(prepared.variants));
9443
+ }
9444
+ async prepare(blockState, properties) {
9445
+ const state = { ...await BlockStateResolver.defaults(blockState), ...properties };
9446
+ const groups = BlockStateResolver.matching(blockState, state);
9447
+ if (groups.length !== 1) return void 0;
9448
+ const variants = groups[0];
9449
+ const choices = Array.isArray(variants) ? variants : [variants];
9450
+ if (!choices.length) return void 0;
9451
+ const templates = /* @__PURE__ */ new Map();
9452
+ for (const variant of choices) {
9453
+ const model = await Models.getMerged(AssetKey.parse("models", variant.model));
9454
+ if (!model) return void 0;
9455
+ const atlas = await UVMapper.getAtlas(model);
9456
+ const rotation = BlockStateResolver.rotation(variant);
9457
+ if (!atlas || atlas.hasAnimation || !ModelCulling.isOpaqueFullCube(atlas) || atlas.image.width > this.maxAtlasSize || atlas.image.height > this.maxAtlasSize || ModelCulling.toLocalMask(63, rotation) !== 63) return void 0;
9458
+ const tints = await BlockTints.get(blockState.key, state, model);
9459
+ let models = this.models.get(model);
9460
+ if (!models) this.models.set(model, models = /* @__PURE__ */ new Map());
9461
+ const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints]);
9462
+ const template = await cached(models, key, async () => {
9463
+ const object = new SectionModel(model, {
9464
+ instanceMeshes: false,
9465
+ mergeMeshes: false,
9466
+ tints,
9467
+ uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0) ? [rotation.x, rotation.y, rotation.z] : void 0
9468
+ });
9469
+ await object.init();
9470
+ const geometry = object.children[0].geometry;
9471
+ geometry.applyMatrix4(new Matrix46().makeRotationFromEuler(rotation));
9472
+ object.clear();
9473
+ const cullFaces = CUBE_FACES.map((face) => {
9474
+ const local = CUBE_FACES.findIndex((name) => name === atlas.model.elements[0].faces[face]?.cullface);
9475
+ if (local < 0) return 0;
9476
+ const normal = new Vector311(...CUBE_FACE_OFFSETS[local]).applyEuler(rotation);
9477
+ 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);
9478
+ return 1 << world;
9479
+ });
9480
+ const template2 = { geometry, atlas, cullFaces };
9481
+ this.templates.add(template2);
9482
+ return template2;
9483
+ });
9484
+ templates.set(variant, template);
9485
+ }
9486
+ return { variants, templates };
9487
+ }
9488
+ clear() {
9489
+ for (const template of this.templates) template.geometry.dispose();
9490
+ this.templates.clear();
9491
+ this.states = /* @__PURE__ */ new WeakMap();
9492
+ this.models = /* @__PURE__ */ new WeakMap();
9493
+ }
9494
+ };
9495
+
9398
9496
  // src/world/MineRenderWorld.ts
9399
- import { Vector3 as Vector311 } from "three";
9400
9497
  var MineRenderWorld = class {
9401
- constructor(scene) {
9498
+ constructor(scene, options = {}) {
9402
9499
  this._chunks = /* @__PURE__ */ new Map();
9403
9500
  this.pendingCulling = /* @__PURE__ */ new Map();
9404
9501
  this.scene = scene;
9502
+ if (options.sectionMeshing) this.sectionModels = new SectionModels(options.maxAtlasSize);
9405
9503
  }
9406
9504
  getBlockAt(posOrX, y, z) {
9407
9505
  if (typeof posOrX == "number") {
9408
- return this.getBlockAt(new Vector311(posOrX, y, z));
9506
+ return this.getBlockAt(new Vector312(posOrX, y, z));
9409
9507
  }
9410
9508
  if (isTripleArray(posOrX)) {
9411
- return this.getBlockAt(new Vector311(posOrX[0], posOrX[1], posOrX[2]));
9509
+ return this.getBlockAt(new Vector312(posOrX[0], posOrX[1], posOrX[2]));
9412
9510
  }
9413
9511
  this.validatePosBounds(posOrX);
9414
9512
  return this.getChunkAt(posOrX)?.getBlockAt(posOrX);
9415
9513
  }
9416
9514
  async setBlockAt(posOrX, yOrBlock, z, block) {
9417
9515
  if (typeof posOrX == "number") {
9418
- return this.setBlockAt(new Vector311(posOrX, yOrBlock, z), block);
9516
+ return this.setBlockAt(new Vector312(posOrX, yOrBlock, z), block);
9419
9517
  }
9420
9518
  if (isTripleArray(posOrX)) {
9421
- return this.setBlockAt(new Vector311(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
9519
+ return this.setBlockAt(new Vector312(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
9422
9520
  }
9423
9521
  return this.placeBlock(posOrX, yOrBlock);
9424
9522
  }
@@ -9439,7 +9537,7 @@ var MineRenderWorld = class {
9439
9537
  const collect = async (positions2) => {
9440
9538
  for (const pos of positions2) changes.set(pos.toArray().join(","), pos);
9441
9539
  };
9442
- const place = (block) => this.placeBlock(new Vector311(...block.position), block, collect);
9540
+ const place = (block) => this.placeBlock(new Vector312(...block.position), block, collect);
9443
9541
  const keys = /* @__PURE__ */ new Map();
9444
9542
  for (const block of multiblock.blocks) {
9445
9543
  if (Chunk.isAir(block)) continue;
@@ -9511,12 +9609,13 @@ var MineRenderWorld = class {
9511
9609
  for (const chunk of chunks) {
9512
9610
  await chunk.dispose();
9513
9611
  }
9612
+ this.sectionModels?.clear();
9514
9613
  }
9515
9614
  updateCulling(positions) {
9516
9615
  for (const pos of positions) {
9517
9616
  this.pendingCulling.set(pos.toArray().join(","), pos.clone());
9518
9617
  for (const offset of CUBE_FACE_OFFSETS) {
9519
- const neighbor = pos.clone().add(new Vector311(...offset));
9618
+ const neighbor = pos.clone().add(new Vector312(...offset));
9520
9619
  this.pendingCulling.set(neighbor.toArray().join(","), neighbor);
9521
9620
  }
9522
9621
  }
@@ -9525,17 +9624,22 @@ var MineRenderWorld = class {
9525
9624
  while (this.pendingCulling.size) {
9526
9625
  const batch = [...this.pendingCulling.values()];
9527
9626
  this.pendingCulling.clear();
9627
+ const changed = /* @__PURE__ */ new Set();
9528
9628
  for (const pos of batch) {
9529
- const block = this.getBlockAt(pos)?.object;
9530
- if (!block) continue;
9629
+ const chunk = this.getChunkAt(pos);
9630
+ if (!chunk) continue;
9631
+ changed.add(chunk);
9632
+ if (!chunk.getBlockAt(pos)) continue;
9531
9633
  let mask = 0;
9532
9634
  for (const [face, offset] of CUBE_FACE_OFFSETS.entries()) {
9533
- if (this.getBlockAt(pos.clone().add(new Vector311(...offset)))?.object.isOccluding) {
9635
+ const neighbor = pos.clone().add(new Vector312(...offset));
9636
+ if (this.getChunkAt(neighbor)?.isOccludingAt(neighbor)) {
9534
9637
  mask |= 1 << face;
9535
9638
  }
9536
9639
  }
9537
- await block.setCullMask(mask);
9640
+ await chunk.setCullMaskAt(pos, mask);
9538
9641
  }
9642
+ for (const chunk of changed) chunk.rebuildSectionMesh();
9539
9643
  }
9540
9644
  } finally {
9541
9645
  this.culling = void 0;
@@ -9551,7 +9655,8 @@ var MineRenderWorld = class {
9551
9655
  Math.floor(pos.x / 16),
9552
9656
  Math.floor(pos.y / 16),
9553
9657
  Math.floor(pos.z / 16),
9554
- (positions) => this.updateCulling(positions)
9658
+ (positions) => this.updateCulling(positions),
9659
+ this.sectionModels
9555
9660
  );
9556
9661
  this._chunks.set(key, chunk);
9557
9662
  }
@@ -9561,14 +9666,14 @@ var MineRenderWorld = class {
9561
9666
  return this._chunks.get(this.worldPosToChunkKey(pos));
9562
9667
  }
9563
9668
  static worldToScenePosition(pos) {
9564
- return new Vector311(
9669
+ return new Vector312(
9565
9670
  pos.x * 16,
9566
9671
  pos.y * 16,
9567
9672
  pos.z * 16
9568
9673
  );
9569
9674
  }
9570
9675
  static sceneToWorldPosition(pos) {
9571
- return new Vector311(
9676
+ return new Vector312(
9572
9677
  pos.x / 16,
9573
9678
  pos.y / 16,
9574
9679
  pos.z / 16
@@ -9587,12 +9692,118 @@ var MineRenderWorld = class {
9587
9692
  }
9588
9693
  };
9589
9694
 
9695
+ // src/world/SectionMesh.ts
9696
+ import { BufferGeometry as BufferGeometry2, Float32BufferAttribute as Float32BufferAttribute4, Group, Mesh as Mesh7 } from "three";
9697
+ var SectionMesh = class _SectionMesh extends Group {
9698
+ constructor() {
9699
+ super(...arguments);
9700
+ this.ownedMeshes = [];
9701
+ }
9702
+ static build(entries, maxAtlasSize = 2048) {
9703
+ if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
9704
+ const visible = entries.filter((entry) => entry.template.cullFaces.some((direction) => !(entry.cullMask & direction)));
9705
+ const atlases = [...new Set(visible.map((entry) => entry.template.atlas))].sort((a, b) => b.image.height - a.image.height);
9706
+ const pages = [];
9707
+ const locations = /* @__PURE__ */ new Map();
9708
+ for (const atlas of atlases) {
9709
+ const { width, height } = atlas.image;
9710
+ if (width > maxAtlasSize || height > maxAtlasSize) throw new RangeError("Model atlas exceeds the section atlas size limit");
9711
+ let selected;
9712
+ for (const page of pages) {
9713
+ const nextRow = page.x + width > maxAtlasSize;
9714
+ if ((nextRow ? page.y + page.rowHeight : page.y) + height > maxAtlasSize) continue;
9715
+ if (nextRow) {
9716
+ page.y += page.rowHeight;
9717
+ page.x = 0;
9718
+ page.rowHeight = 0;
9719
+ }
9720
+ selected = page;
9721
+ break;
9722
+ }
9723
+ if (!selected) {
9724
+ selected = { placements: [], entries: [], x: 0, y: 0, rowHeight: 0, width: 0, height: 0 };
9725
+ pages.push(selected);
9726
+ }
9727
+ const placement = { atlas, x: selected.x, y: selected.y };
9728
+ selected.placements.push(placement);
9729
+ selected.x += width;
9730
+ selected.rowHeight = Math.max(selected.rowHeight, height);
9731
+ selected.width = Math.max(selected.width, selected.x);
9732
+ selected.height = Math.max(selected.height, selected.y + height);
9733
+ locations.set(atlas, { page: selected, placement });
9734
+ }
9735
+ for (const entry of visible) locations.get(entry.template.atlas).page.entries.push(entry);
9736
+ const section = new _SectionMesh();
9737
+ for (const page of pages) {
9738
+ const canvas = createCanvas2(page.width, page.height);
9739
+ const context = canvas.getContext("2d");
9740
+ for (const { atlas, x, y } of page.placements) {
9741
+ context.drawImage(atlas.image.canvas, x, y);
9742
+ }
9743
+ const positions = [], normals = [], uvs = [], colors = [], indices = [];
9744
+ for (const { index, template, cullMask } of page.entries) {
9745
+ const position = template.geometry.getAttribute("position");
9746
+ const normal = template.geometry.getAttribute("normal");
9747
+ const uv = template.geometry.getAttribute("uv");
9748
+ const color = template.geometry.getAttribute("color");
9749
+ const sourceIndices = template.geometry.getIndex();
9750
+ const placement = locations.get(template.atlas).placement;
9751
+ const offsetX = index % 16 * 16;
9752
+ const offsetY = Math.floor(index / 256) * 16;
9753
+ const offsetZ = Math.floor(index / 16) % 16 * 16;
9754
+ for (let face = 0; face < 6; face++) {
9755
+ if (cullMask & template.cullFaces[face]) continue;
9756
+ const start = positions.length / 3;
9757
+ for (let corner = 0; corner < 4; corner++) {
9758
+ const vertex = face * 4 + corner;
9759
+ positions.push(position.getX(vertex) + offsetX, position.getY(vertex) + offsetY, position.getZ(vertex) + offsetZ);
9760
+ normals.push(normal.getX(vertex), normal.getY(vertex), normal.getZ(vertex));
9761
+ uvs.push(
9762
+ (placement.x + uv.getX(vertex) * template.atlas.image.width) / page.width,
9763
+ 1 - (placement.y + (1 - uv.getY(vertex)) * template.atlas.image.height) / page.height
9764
+ );
9765
+ colors.push(color?.getX(vertex) ?? 1, color?.getY(vertex) ?? 1, color?.getZ(vertex) ?? 1);
9766
+ }
9767
+ for (let corner = 0; corner < 6; corner++) indices.push(start + sourceIndices.getX(face * 6 + corner) - face * 4);
9768
+ }
9769
+ }
9770
+ const geometry = new BufferGeometry2();
9771
+ geometry.setAttribute("position", new Float32BufferAttribute4(positions, 3));
9772
+ geometry.setAttribute("normal", new Float32BufferAttribute4(normals, 3));
9773
+ geometry.setAttribute("uv", new Float32BufferAttribute4(uvs, 2));
9774
+ geometry.setAttribute("color", new Float32BufferAttribute4(colors, 3));
9775
+ geometry.setIndex(indices);
9776
+ geometry.computeBoundingBox();
9777
+ geometry.computeBoundingSphere();
9778
+ const material = Materials.createShadedCanvasMaterial(canvas, false);
9779
+ material.vertexColors = true;
9780
+ const texture = material.uniforms?.map?.value ?? material.map;
9781
+ const mesh = new Mesh7(geometry, material);
9782
+ section.add(mesh);
9783
+ section.ownedMeshes.push({ mesh, texture });
9784
+ }
9785
+ return section;
9786
+ }
9787
+ dispose() {
9788
+ for (const { mesh, texture } of this.ownedMeshes.splice(0)) {
9789
+ mesh.removeFromParent();
9790
+ mesh.geometry.dispose();
9791
+ mesh.material.dispose();
9792
+ texture?.dispose();
9793
+ }
9794
+ this.removeFromParent();
9795
+ }
9796
+ };
9797
+
9590
9798
  // src/world/Chunk.ts
9591
9799
  var Chunk = class _Chunk {
9592
- constructor(scene, x, y, z, onBlocksChanged) {
9800
+ constructor(scene, x, y, z, onBlocksChanged, sectionModels) {
9593
9801
  this.onBlocksChanged = onBlocksChanged;
9802
+ this.sectionModels = sectionModels;
9594
9803
  this.data = new ChunkData();
9595
9804
  this.renderedBlocks = /* @__PURE__ */ new Map();
9805
+ this.sectionBlocks = /* @__PURE__ */ new Map();
9806
+ this.meshDirty = false;
9596
9807
  this.scene = scene;
9597
9808
  this.x = x;
9598
9809
  this.y = y;
@@ -9600,10 +9811,10 @@ var Chunk = class _Chunk {
9600
9811
  }
9601
9812
  getBlockAt(posOrX, y, z) {
9602
9813
  if (typeof posOrX == "number") {
9603
- return this.getBlockAt(new Vector312(posOrX, y, z));
9814
+ return this.getBlockAt(new Vector313(posOrX, y, z));
9604
9815
  }
9605
9816
  if (isTripleArray(posOrX)) {
9606
- return this.getBlockAt(new Vector312(posOrX[0], posOrX[1], posOrX[2]));
9817
+ return this.getBlockAt(new Vector313(posOrX[0], posOrX[1], posOrX[2]));
9607
9818
  }
9608
9819
  const pos = this.worldPosToChunkPos(posOrX);
9609
9820
  const index = _Chunk.chunkPosToBlockIndex(pos);
@@ -9611,10 +9822,10 @@ var Chunk = class _Chunk {
9611
9822
  }
9612
9823
  async setBlockAt(posOrX, yOrBlock, z, block) {
9613
9824
  if (typeof posOrX == "number") {
9614
- return this.setBlockAt(new Vector312(posOrX, yOrBlock, z), block);
9825
+ return this.setBlockAt(new Vector313(posOrX, yOrBlock, z), block);
9615
9826
  }
9616
9827
  if (isTripleArray(posOrX)) {
9617
- return this.setBlockAt(new Vector312(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
9828
+ return this.setBlockAt(new Vector313(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
9618
9829
  }
9619
9830
  const worldPos = posOrX;
9620
9831
  const pos = this.worldPosToChunkPos(worldPos);
@@ -9631,7 +9842,8 @@ var Chunk = class _Chunk {
9631
9842
  const index = _Chunk.chunkPosToBlockIndex(pos);
9632
9843
  this.data.set(index, block);
9633
9844
  const readBlock = this.data.snapshot(index);
9634
- this.renderedBlocks.get(index)?.object.removeFromScene();
9845
+ this.renderedBlocks.get(index)?.object?.removeFromScene();
9846
+ if (this.sectionBlocks.delete(index)) this.meshDirty = true;
9635
9847
  this.renderedBlocks.delete(index);
9636
9848
  let object;
9637
9849
  try {
@@ -9642,13 +9854,19 @@ var Chunk = class _Chunk {
9642
9854
  this.data.set(index, void 0);
9643
9855
  return void 0;
9644
9856
  }
9645
- object = await this.scene.addBlock(blockState, {
9646
- mergeMeshes: true,
9647
- instanceMeshes: true,
9648
- maxInstanceCount: 2e3,
9649
- initialState: stored.properties
9650
- });
9651
- object.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
9857
+ const template = await this.sectionModels?.get(blockState, stored.properties);
9858
+ if (template) {
9859
+ this.sectionBlocks.set(index, { index, template, cullMask: 0 });
9860
+ this.meshDirty = true;
9861
+ } else {
9862
+ object = await this.scene.addBlock(blockState, {
9863
+ mergeMeshes: true,
9864
+ instanceMeshes: true,
9865
+ maxInstanceCount: 2e3,
9866
+ initialState: stored.properties
9867
+ });
9868
+ object.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
9869
+ }
9652
9870
  const info = { get block() {
9653
9871
  return readBlock();
9654
9872
  }, object };
@@ -9662,11 +9880,43 @@ var Chunk = class _Chunk {
9662
9880
  await onBlocksChanged?.([worldPos]);
9663
9881
  }
9664
9882
  }
9883
+ isOccludingAt(pos) {
9884
+ const index = _Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos));
9885
+ return this.sectionBlocks.has(index) || (this.renderedBlocks.get(index)?.object?.isOccluding ?? false);
9886
+ }
9887
+ async setCullMaskAt(pos, mask) {
9888
+ const index = _Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos));
9889
+ const entry = this.sectionBlocks.get(index);
9890
+ if (entry) {
9891
+ if (entry.cullMask !== mask) {
9892
+ entry.cullMask = mask;
9893
+ this.meshDirty = true;
9894
+ }
9895
+ } else {
9896
+ await this.renderedBlocks.get(index)?.object?.setCullMask(mask);
9897
+ }
9898
+ }
9899
+ rebuildSectionMesh() {
9900
+ if (!this.meshDirty) return;
9901
+ const next = this.sectionBlocks.size ? SectionMesh.build([...this.sectionBlocks.values()], this.sectionModels.maxAtlasSize) : void 0;
9902
+ this.sectionMesh?.dispose();
9903
+ this.sectionMesh = next;
9904
+ if (next) {
9905
+ next.position.set(this.x * 256, this.y * 256, this.z * 256);
9906
+ this.scene.add(next);
9907
+ }
9908
+ this.scene.dirty = true;
9909
+ this.meshDirty = false;
9910
+ }
9665
9911
  async clear(onBlocksChanged = this.onBlocksChanged) {
9666
9912
  const positions = [...this.renderedBlocks.keys()].map((index) => this.chunkPosToWorldPos(
9667
- new Vector312(index % 16, Math.floor(index / 256), Math.floor(index / 16) % 16)
9913
+ new Vector313(index % 16, Math.floor(index / 256), Math.floor(index / 16) % 16)
9668
9914
  ));
9669
- for (const info of this.renderedBlocks.values()) info.object.removeFromScene();
9915
+ for (const info of this.renderedBlocks.values()) info.object?.removeFromScene();
9916
+ this.sectionBlocks.clear();
9917
+ this.sectionMesh?.dispose();
9918
+ this.sectionMesh = void 0;
9919
+ this.meshDirty = false;
9670
9920
  this.renderedBlocks.clear();
9671
9921
  this.data.clear();
9672
9922
  await onBlocksChanged?.(positions);
@@ -9684,14 +9934,14 @@ var Chunk = class _Chunk {
9684
9934
  return pos.y * 16 * 16 + pos.z * 16 + pos.x;
9685
9935
  }
9686
9936
  worldPosToChunkPos(pos) {
9687
- return new Vector312(
9937
+ return new Vector313(
9688
9938
  pos.x - this.x * 16,
9689
9939
  pos.y - this.y * 16,
9690
9940
  pos.z - this.z * 16
9691
9941
  );
9692
9942
  }
9693
9943
  chunkPosToWorldPos(pos) {
9694
- return new Vector312(
9944
+ return new Vector313(
9695
9945
  this.x * 16 + pos.x,
9696
9946
  this.y * 16 + pos.y,
9697
9947
  this.z * 16 + pos.z
@@ -9714,6 +9964,7 @@ export {
9714
9964
  BasicAssetKey,
9715
9965
  BatchedExecutor,
9716
9966
  BlockObject,
9967
+ BlockStateResolver,
9717
9968
  BlockStates,
9718
9969
  BlockTints,
9719
9970
  BrowserArchiveProxy,
@@ -9772,6 +10023,8 @@ export {
9772
10023
  SceneStats,
9773
10024
  SceneStatsDisplay,
9774
10025
  SchematicParser,
10026
+ SectionMesh,
10027
+ SectionModels,
9775
10028
  SkinImage,
9776
10029
  SkinObject,
9777
10030
  SkinPart,