minerender 2.1.0-alpha.41 → 2.1.0-alpha.43

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
  };
@@ -766,11 +766,11 @@ var ImageLoader = class _ImageLoader {
766
766
  static async loadCanvasData(src) {
767
767
  const keyStr = serializeImageKey({ src });
768
768
  const info = this.getInfo(src);
769
- const cached = Caching.rawImageCache.getIfPresent(keyStr);
769
+ const cached2 = Caching.rawImageCache.getIfPresent(keyStr);
770
770
  try {
771
771
  return await this.infoToCanvasData(await info);
772
772
  } catch (err) {
773
- if (cached && Caching.rawImageCache.getIfPresent(keyStr) === cached) {
773
+ if (cached2 && Caching.rawImageCache.getIfPresent(keyStr) === cached2) {
774
774
  Caching.rawImageCache.invalidate(keyStr);
775
775
  }
776
776
  throw err;
@@ -905,8 +905,8 @@ var Textures = class _Textures {
905
905
  }
906
906
  static getImage(key) {
907
907
  const keyStr = serializeTextureKey(key);
908
- const cached = Caching.textureCache.peek(keyStr);
909
- if (cached && TextureLoader.hasFailed(cached)) {
908
+ const cached2 = Caching.textureCache.peek(keyStr);
909
+ if (cached2 && TextureLoader.hasFailed(cached2)) {
910
910
  Caching.textureCache.invalidate(keyStr);
911
911
  }
912
912
  return Caching.textureCache.get(keyStr, (k) => {
@@ -1173,12 +1173,12 @@ var AssetKey = class _AssetKey extends BasicAssetKey {
1173
1173
  return a.join("/");
1174
1174
  }
1175
1175
  getFullPath() {
1176
- let p11 = [];
1176
+ let p10 = [];
1177
1177
  if (this.type) {
1178
- p11.push(this.type);
1178
+ p10.push(this.type);
1179
1179
  }
1180
- p11.push(this.path);
1181
- return p11.join("/");
1180
+ p10.push(this.path);
1181
+ return p10.join("/");
1182
1182
  }
1183
1183
  toNamespacedString() {
1184
1184
  return this.namespace + ":" + this.getFullPath();
@@ -1265,7 +1265,7 @@ var ModelMerger = class {
1265
1265
  merged.display = { ...inheritedDisplay, ...DisplayTransforms.withHandFallbacks(source.display) };
1266
1266
  }
1267
1267
  }
1268
- merged.hierarchy = models.map((m) => m.parent).filter((p11) => `${p11}`);
1268
+ merged.hierarchy = models.map((m) => m.parent).filter((p10) => `${p10}`);
1269
1269
  merged.hierarchy.push(`${merged.parent}`);
1270
1270
  return merged;
1271
1271
  }
@@ -1972,7 +1972,7 @@ var ModelTextures = class {
1972
1972
  static async get(key) {
1973
1973
  const keyStr = key.serialize();
1974
1974
  const pending = this.preload(key);
1975
- const cached = Caching.textureAssetCache.getIfPresent(keyStr);
1975
+ const cached2 = Caching.textureAssetCache.getIfPresent(keyStr);
1976
1976
  try {
1977
1977
  const asset = await pending;
1978
1978
  if (asset) {
@@ -1980,7 +1980,7 @@ var ModelTextures = class {
1980
1980
  }
1981
1981
  return void 0;
1982
1982
  } catch (err) {
1983
- if (cached && Caching.textureAssetCache.getIfPresent(keyStr) === cached) {
1983
+ if (cached2 && Caching.textureAssetCache.getIfPresent(keyStr) === cached2) {
1984
1984
  Caching.textureAssetCache.invalidate(keyStr);
1985
1985
  }
1986
1986
  throw err;
@@ -3048,6 +3048,10 @@ var _SceneObject = class _SceneObject extends Object3D2 {
3048
3048
  this.setMatrixAt(index, new Matrix43());
3049
3049
  return reference;
3050
3050
  }
3051
+ /** Returns the live reference for a raycast hit on this object's instance mesh. */
3052
+ getInstanceReference(mesh, index) {
3053
+ return mesh === this.instanceMesh ? this.instanceReferences.get(index) : void 0;
3054
+ }
3051
3055
  isInstanceActive(index, reference) {
3052
3056
  return this.instanceReferences.get(index) === reference;
3053
3057
  }
@@ -3130,9 +3134,9 @@ var _SceneObject = class _SceneObject extends Object3D2 {
3130
3134
  }
3131
3135
  getRotationAt(index, euler = new Euler3()) {
3132
3136
  if (!this.isInstanced) throw new MineRenderError("Object is not instanced");
3133
- const matrix = this.getMatrixAt(index);
3134
- euler.setFromRotationMatrix(matrix);
3135
- return euler;
3137
+ const rotation = new Quaternion3();
3138
+ this.getMatrixAt(index).decompose(new Vector35(), rotation, new Vector35());
3139
+ return euler.setFromQuaternion(rotation);
3136
3140
  }
3137
3141
  setScaleAt(index, scale) {
3138
3142
  if (!this.isInstanced) throw new MineRenderError("Object is not instanced");
@@ -3388,11 +3392,12 @@ var GuiObject = class extends SceneObject {
3388
3392
  // src/inspector/SceneInspector.ts
3389
3393
  import { Raycaster, Vector2 as Vector22 } from "three";
3390
3394
  var p7 = prefix("SceneInspector");
3391
- var help = "<span>Ctrl+Click to Select<br/></span><br/>";
3395
+ var help = "<span>Ctrl/Cmd+Click to Select<br/></span><br/>";
3392
3396
  var SceneInspector = class {
3393
3397
  constructor(renderer) {
3394
3398
  this.renderer = renderer;
3395
3399
  this.mouse = new Vector22();
3400
+ this.listeners = new AbortController();
3396
3401
  this.objectInfoContainer = document.createElement("div");
3397
3402
  this.objectControlsContainer = document.createElement("div");
3398
3403
  this.raycaster = new Raycaster();
@@ -3402,13 +3407,44 @@ var SceneInspector = class {
3402
3407
  this.objectInfoContainer.classList.add("minerender-inspector", "minerender-object-info");
3403
3408
  this.objectControlsContainer.classList.add("minerender-inspector", "minerender-object-controls");
3404
3409
  this.objectInfoContainer.innerHTML = help;
3405
- document.addEventListener("click", this.onClick.bind(this), {
3406
- passive: true
3407
- });
3410
+ const canvas = this.renderer.renderer.domElement;
3411
+ const options = { signal: this.listeners.signal };
3412
+ canvas.addEventListener("pointerdown", (event) => {
3413
+ this.pointer = event.button === 0 && (event.ctrlKey || event.metaKey) ? event : void 0;
3414
+ }, options);
3415
+ canvas.addEventListener("pointermove", (event) => {
3416
+ if (this.pointer?.pointerId === event.pointerId && Math.hypot(event.clientX - this.pointer.clientX, event.clientY - this.pointer.clientY) > 5) {
3417
+ this.pointer = void 0;
3418
+ }
3419
+ }, options);
3420
+ canvas.addEventListener("pointerup", (event) => {
3421
+ const start = this.pointer;
3422
+ this.pointer = void 0;
3423
+ if (start?.pointerId === event.pointerId && Math.hypot(event.clientX - start.clientX, event.clientY - start.clientY) <= 5) {
3424
+ this.onClick(event);
3425
+ }
3426
+ }, options);
3427
+ canvas.addEventListener("pointercancel", () => {
3428
+ this.pointer = void 0;
3429
+ }, options);
3430
+ canvas.addEventListener("pointerleave", () => {
3431
+ this.pointer = void 0;
3432
+ }, options);
3433
+ canvas.addEventListener("contextmenu", (event) => {
3434
+ if (this.pointer?.ctrlKey) event.preventDefault();
3435
+ }, options);
3436
+ }
3437
+ dispose() {
3438
+ this.listeners.abort();
3439
+ this.pointer = void 0;
3440
+ this.selectedObject = void 0;
3441
+ this.objectInfoContainer.remove();
3442
+ this.objectControlsContainer.remove();
3408
3443
  }
3409
3444
  findMeshChildren(from, out = []) {
3445
+ if (!from.visible) return out;
3410
3446
  for (let c of from.children) {
3411
- if (isMesh(c)) {
3447
+ if (c.visible && isMesh(c)) {
3412
3448
  out.push(c);
3413
3449
  }
3414
3450
  this.findMeshChildren(c, out);
@@ -3416,50 +3452,43 @@ var SceneInspector = class {
3416
3452
  return out;
3417
3453
  }
3418
3454
  onClick(event) {
3419
- if (event.target?.nodeName !== "CANVAS") return;
3420
- if (!event.ctrlKey) return;
3421
- setTimeout(() => {
3422
- this.mouse.x = event.clientX / window.innerWidth * 2 - 1;
3423
- this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
3424
- this.raycaster.setFromCamera(this.mouse, this.renderer.camera);
3425
- let start = Date.now();
3426
- let targets = this.findMeshChildren(this.renderer.scene);
3427
- const intersects = this.raycaster.intersectObjects(targets, false);
3428
- console.debug("Raycast took " + (Date.now() - start) + "ms for " + targets.length + " possible targets with " + intersects.length + " results");
3429
- if (intersects.length > 0) {
3430
- this.handleRaycasterObjects(intersects);
3431
- }
3432
- });
3455
+ const canvas = this.renderer.renderer.domElement;
3456
+ if (event.target !== canvas || event.button !== 0 || !(event.ctrlKey || event.metaKey)) return;
3457
+ const rect = canvas.getBoundingClientRect();
3458
+ const x = event.clientX - rect.left, y = event.clientY - rect.top;
3459
+ if (rect.width <= 0 || rect.height <= 0 || x < 0 || y < 0 || x >= rect.width || y >= rect.height) return;
3460
+ this.mouse.set(x / rect.width * 2 - 1, 1 - y / rect.height * 2);
3461
+ this.renderer.camera.updateWorldMatrix(true, false);
3462
+ this.renderer.scene.updateMatrixWorld(true);
3463
+ this.raycaster.setFromCamera(this.mouse, this.renderer.camera);
3464
+ this.handleRaycasterObjects(this.raycaster.intersectObjects(this.findMeshChildren(this.renderer.scene), false));
3433
3465
  }
3434
3466
  appendTo(el) {
3435
3467
  el.append(this.objectInfoContainer);
3436
3468
  el.append(this.objectControlsContainer);
3437
3469
  }
3438
3470
  handleRaycasterObjects(intersections) {
3439
- console.debug(intersections);
3440
- const firstIntersection = intersections[0];
3441
- if (firstIntersection && firstIntersection.object) {
3442
- const firstObject = firstIntersection.object;
3443
- let targetIntersection = void 0;
3444
- let targetObject = void 0;
3445
- const firstSceneObjectIntersection = intersections.find((i) => i.object && i.object.parent && isSceneObject(i.object.parent));
3446
- if (firstSceneObjectIntersection && firstSceneObjectIntersection.object) {
3447
- targetIntersection = firstSceneObjectIntersection;
3448
- targetObject = firstSceneObjectIntersection.object;
3449
- } else {
3450
- targetIntersection = firstIntersection;
3451
- targetObject = firstObject;
3471
+ const hit = intersections[0];
3472
+ if (!hit) return;
3473
+ const instance = this.getIntersectionInstance(hit);
3474
+ this.selectObject(instance?.instanceable ?? hit.object, hit, instance);
3475
+ }
3476
+ getIntersectionInstance(hit) {
3477
+ if (hit?.instanceId === void 0) return void 0;
3478
+ for (let parent = hit.object.parent; parent; parent = parent.parent) {
3479
+ if (isSceneObject(parent)) {
3480
+ const instance = parent.getInstanceReference(hit.object, hit.instanceId);
3481
+ if (instance) return instance;
3452
3482
  }
3453
- if (!targetIntersection || !targetObject) return;
3454
- this.selectObject(targetObject, targetIntersection);
3455
3483
  }
3484
+ return void 0;
3456
3485
  }
3457
- selectObject(targetObject, targetIntersection) {
3486
+ selectObject(targetObject, targetIntersection, instance = this.getIntersectionInstance(targetIntersection)) {
3458
3487
  this.selectedObject = targetObject;
3459
3488
  console.log(p7, "selected", targetObject);
3460
- this.redraw(targetObject, targetIntersection);
3489
+ this.redraw(targetObject, targetIntersection, instance);
3461
3490
  }
3462
- redraw(targetObject, targetIntersection) {
3491
+ redraw(targetObject, targetIntersection, instance) {
3463
3492
  this.objectInfoContainer.innerHTML = help;
3464
3493
  if (targetIntersection) {
3465
3494
  this.addInfoLine("Distance", "D", targetIntersection.distance);
@@ -3472,8 +3501,8 @@ var SceneInspector = class {
3472
3501
  if (isSceneObject(targetObject.parent)) {
3473
3502
  }
3474
3503
  }
3475
- if ("atlas" in targetObject) {
3476
- const atlas = targetObject["atlas"];
3504
+ const atlas = "atlas" in targetObject ? targetObject["atlas"] : void 0;
3505
+ if (atlas?.image.canvas) {
3477
3506
  const img = atlas.image.canvas;
3478
3507
  img.style.width = "100px";
3479
3508
  img.style.height = "100px";
@@ -3482,28 +3511,31 @@ var SceneInspector = class {
3482
3511
  this.objectInfoContainer.prepend(img);
3483
3512
  }
3484
3513
  this.objectControlsContainer.innerHTML = "";
3485
- this.addControls(targetObject, targetIntersection);
3514
+ this.addControls(targetObject, targetIntersection, instance);
3486
3515
  }
3487
- addControls(object, intersection) {
3516
+ addControls(object, intersection, instance) {
3488
3517
  const container = document.createElement("div");
3489
3518
  container.append(this.separator("Select Parent/Child"));
3490
3519
  if (object.parent) {
3491
3520
  container.append(this.buttonControl("Select Parent " + object.parent.constructor.name + " " + object.parent.name, "P", () => {
3492
- this.selectObject(object.parent, intersection);
3521
+ this.selectObject(object.parent, intersection, instance);
3493
3522
  }));
3494
3523
  }
3495
3524
  if (object.children.length > 0) {
3496
3525
  let i = 1;
3497
3526
  for (let child of object.children) {
3498
3527
  container.append(this.buttonControl("Select Child " + child.constructor.name + " " + child.name, "C" + i++, () => {
3499
- this.selectObject(child, intersection);
3528
+ this.selectObject(child, intersection, instance);
3500
3529
  }));
3501
3530
  }
3502
3531
  }
3503
3532
  container.append(this.separator());
3504
- container.append(this.toggleControl("Visibility", "V", object.visible, (v) => object.visible = v));
3533
+ const selectedInstance = instance && (object === instance.instanceable || object === intersection?.object) ? instance : void 0;
3534
+ if (!selectedInstance) {
3535
+ container.append(this.toggleControl("Visibility", "V", object.visible, (v) => object.visible = v));
3536
+ }
3505
3537
  container.append(this.separator("Mesh"));
3506
- this.addObjectControls(object, intersection, container);
3538
+ this.addObjectControls(object, intersection, container, selectedInstance);
3507
3539
  container.append(this.separator());
3508
3540
  this.objectControlsContainer.append(container);
3509
3541
  }
@@ -3515,78 +3547,26 @@ var SceneInspector = class {
3515
3547
  this.objectInfoContainer.append(document.createElement("br"));
3516
3548
  return el;
3517
3549
  }
3518
- addObjectControls(target, intersection, container) {
3550
+ addObjectControls(target, intersection, container, instance) {
3519
3551
  const posRange = 16 * 16;
3520
3552
  const rotRange = 360;
3521
3553
  const scaleRange = 4;
3522
- if (typeof intersection !== "undefined" && typeof intersection.instanceId !== "undefined" && isSceneObject(target) && target.isInstanced) {
3523
- const scObj = target;
3524
- container.append(this.separator("Position"));
3525
- let pos = scObj.getPositionAt(intersection.instanceId);
3526
- container.append(this.rangeControl("X Position", "X", -posRange + pos.x, posRange + pos.x, pos.x, 1, (v) => {
3527
- pos = scObj.getPositionAt(intersection.instanceId);
3528
- pos.x = v;
3529
- scObj.setPositionAt(intersection.instanceId, pos);
3530
- }));
3531
- container.append(this.rangeControl("Y Position", "Y", -posRange + pos.y, posRange + pos.y, pos.y, 1, (v) => {
3532
- pos = scObj.getPositionAt(intersection.instanceId);
3533
- pos.y = v;
3534
- scObj.setPositionAt(intersection.instanceId, pos);
3535
- }));
3536
- container.append(this.rangeControl("Z Position", "Z", -posRange + pos.z, posRange + pos.z, pos.z, 1, (v) => {
3537
- pos = scObj.getPositionAt(intersection.instanceId);
3538
- pos.z = v;
3539
- scObj.setPositionAt(intersection.instanceId, pos);
3540
- }));
3541
- container.append(this.separator("Rotation"));
3542
- let rot = scObj.getRotationAt(intersection.instanceId);
3543
- container.append(this.rangeControl("X Rotation", "X", 0, rotRange, Math.round(toDegrees(rot.x)), 1, (v) => {
3544
- rot = scObj.getRotationAt(intersection.instanceId);
3545
- rot.x = toRadians(v);
3546
- scObj.setRotationAt(intersection.instanceId, rot);
3547
- }));
3548
- container.append(this.rangeControl("Y Rotation", "Y", 0, rotRange, Math.round(toDegrees(rot.y)), 1, (v) => {
3549
- rot = scObj.getRotationAt(intersection.instanceId);
3550
- rot.y = toRadians(v);
3551
- scObj.setRotationAt(intersection.instanceId, rot);
3552
- }));
3553
- container.append(this.rangeControl("Z Rotation", "Z", 0, rotRange, Math.round(toDegrees(rot.z)), 1, (v) => {
3554
- rot = scObj.getRotationAt(intersection.instanceId);
3555
- rot.z = toRadians(v);
3556
- scObj.setRotationAt(intersection.instanceId, rot);
3557
- }));
3558
- container.append(this.separator("Scale"));
3559
- let scl = scObj.getScale();
3560
- container.append(this.rangeControl("X Scale", "X", 0, scaleRange, scl.x, 0.1, (v) => {
3561
- scl = scObj.getScaleAt(intersection.instanceId);
3562
- scl.x = v;
3563
- scObj.setScaleAt(intersection.instanceId, scl);
3564
- }));
3565
- container.append(this.rangeControl("Y Scale", "Y", 0, scaleRange, scl.y, 0.1, (v) => {
3566
- scl = scObj.getScaleAt(intersection.instanceId);
3567
- scl.y = v;
3568
- scObj.setScaleAt(intersection.instanceId, scl);
3569
- }));
3570
- container.append(this.rangeControl("Z Scale", "Z", 0, scaleRange, scl.z, 0.1, (v) => {
3571
- scl = scObj.getScaleAt(intersection.instanceId);
3572
- scl.z = v;
3573
- scObj.setScaleAt(intersection.instanceId, scl);
3574
- }));
3575
- } else if (isTransformable(target)) {
3576
- const parent = target;
3554
+ const transform = instance ?? (isTransformable(target) ? target : void 0);
3555
+ if (transform) {
3556
+ const parent = transform;
3577
3557
  container.append(this.separator("Position"));
3578
3558
  let pos = parent.getPosition();
3579
- container.append(this.rangeControl("X Position", "X", -posRange, posRange, pos.x, 1, (v) => {
3559
+ container.append(this.rangeControl("X Position", "X", pos.x - posRange, pos.x + posRange, pos.x, 1, (v) => {
3580
3560
  pos = parent.getPosition();
3581
3561
  pos.x = v;
3582
3562
  parent.setPosition(pos);
3583
3563
  }));
3584
- container.append(this.rangeControl("Y Position", "Y", -posRange, posRange, pos.y, 1, (v) => {
3564
+ container.append(this.rangeControl("Y Position", "Y", pos.y - posRange, pos.y + posRange, pos.y, 1, (v) => {
3585
3565
  pos = parent.getPosition();
3586
3566
  pos.y = v;
3587
3567
  parent.setPosition(pos);
3588
3568
  }));
3589
- container.append(this.rangeControl("Z Position", "Z", -posRange, posRange, pos.z, 1, (v) => {
3569
+ container.append(this.rangeControl("Z Position", "Z", pos.z - posRange, pos.z + posRange, pos.z, 1, (v) => {
3590
3570
  pos = parent.getPosition();
3591
3571
  pos.z = v;
3592
3572
  parent.setPosition(pos);
@@ -3675,6 +3655,7 @@ var SceneInspector = class {
3675
3655
  toggle.checked = val;
3676
3656
  toggle.addEventListener("change", (e) => {
3677
3657
  change(toggle.checked);
3658
+ this.renderer.scene.dirty = true;
3678
3659
  });
3679
3660
  label.append(toggle);
3680
3661
  label.append(document.createElement("br"));
@@ -3687,7 +3668,6 @@ var SceneInspector = class {
3687
3668
  label.setAttribute("title", name);
3688
3669
  labelText.style.width = "18%";
3689
3670
  labelText.style.display = "inline-block";
3690
- labelText.innerText = `${id} (${val})`;
3691
3671
  const range = document.createElement("input");
3692
3672
  range.setAttribute("type", "range");
3693
3673
  range.style.width = "80%";
@@ -3695,8 +3675,10 @@ var SceneInspector = class {
3695
3675
  range.min = `${min}`;
3696
3676
  range.step = `${step}`;
3697
3677
  range.value = `${val}`;
3678
+ labelText.innerText = `${id} (${range.value})`;
3698
3679
  const onChange = () => {
3699
3680
  change(parseFloat(range.value));
3681
+ this.renderer.scene.dirty = true;
3700
3682
  labelText.innerText = `${id} (${range.value})`;
3701
3683
  };
3702
3684
  range.addEventListener("input", onChange);
@@ -3856,6 +3838,93 @@ var ModelCulling = class {
3856
3838
  }
3857
3839
  };
3858
3840
 
3841
+ // src/model/block/BlockStateResolver.ts
3842
+ import { Euler as Euler5 } from "three";
3843
+ function matchesCondition(condition, state) {
3844
+ return Object.entries(condition).every(([key, value]) => {
3845
+ if (Array.isArray(value)) {
3846
+ if (key === "OR") return value.some((child) => matchesCondition(child, state));
3847
+ if (key === "AND") return value.every((child) => matchesCondition(child, state));
3848
+ return false;
3849
+ }
3850
+ return typeof value === "string" && state[key] !== void 0 && value.split("|").includes(`${state[key]}`);
3851
+ });
3852
+ }
3853
+ var BlockStateResolver = class {
3854
+ static async defaults(blockState) {
3855
+ const defaults = blockState.key ? await BlockStates.getDefaultState(blockState.key) : void 0;
3856
+ const state = {};
3857
+ if (defaults && Object.keys(defaults).length > 0) {
3858
+ for (const key in defaults) state[key] = defaults[key].default;
3859
+ } else if (blockState.variants) {
3860
+ const variant = Object.keys(blockState.variants)[0];
3861
+ if (variant) {
3862
+ for (const property of variant.split(",")) {
3863
+ const [key, value] = property.split("=");
3864
+ state[key] = value;
3865
+ }
3866
+ }
3867
+ } else if (blockState.multipart) {
3868
+ const condition = blockState.multipart.map((part) => part.when).find((when) => when !== void 0 && Object.values(when).every((value) => typeof value === "string"));
3869
+ for (const [key, value] of Object.entries(condition ?? {})) state[key] = value.split("|")[0];
3870
+ }
3871
+ return state;
3872
+ }
3873
+ static select(blockState, state, choose = this.choose) {
3874
+ return this.matching(blockState, state).map(choose);
3875
+ }
3876
+ static matching(blockState, state) {
3877
+ const out = [];
3878
+ if (blockState.variants) {
3879
+ if (Object.keys(blockState.variants).length === 1 && "" in blockState.variants) {
3880
+ out.push(blockState.variants[""]);
3881
+ } else {
3882
+ for (const variantKey in blockState.variants) {
3883
+ const matches = variantKey.split(",").every((property) => {
3884
+ const [key, value] = property.split("=");
3885
+ return `${state[key]}` === `${value}`;
3886
+ });
3887
+ if (matches) out.push(blockState.variants[variantKey]);
3888
+ }
3889
+ }
3890
+ } else if (blockState.multipart) {
3891
+ for (const part of blockState.multipart) {
3892
+ if (part.apply && (!part.when || matchesCondition(part.when, state))) {
3893
+ out.push(part.apply);
3894
+ }
3895
+ }
3896
+ }
3897
+ return out;
3898
+ }
3899
+ static choose(variants) {
3900
+ if (!Array.isArray(variants)) return variants;
3901
+ if (!variants.length) throw new MineRenderError("Blockstate variant arrays must not be empty");
3902
+ const total = variants.reduce((sum, variant) => {
3903
+ const weight = variant.weight ?? 1;
3904
+ if (!Number.isInteger(weight) || weight < 1) {
3905
+ throw new MineRenderError(`Invalid blockstate variant weight: ${weight}`);
3906
+ }
3907
+ return sum + weight;
3908
+ }, 0);
3909
+ let choice = Math.random() * total;
3910
+ for (const variant of variants) {
3911
+ choice -= variant.weight ?? 1;
3912
+ if (choice < 0) return variant;
3913
+ }
3914
+ return variants[variants.length - 1];
3915
+ }
3916
+ static rotation(variant) {
3917
+ const rotation = new Euler5();
3918
+ if (typeof variant.x !== "undefined") {
3919
+ rotation.x = toRadians(clampRotationDegrees(variant.x));
3920
+ }
3921
+ if (typeof variant.y !== "undefined") {
3922
+ rotation.y = toRadians(clampRotationDegrees(typeof variant.x !== "undefined" ? variant.y : 360 - variant.y));
3923
+ }
3924
+ return rotation;
3925
+ }
3926
+ };
3927
+
3859
3928
  // src/model/block/blockTints.json
3860
3929
  var blockTints_default = {
3861
3930
  "minecraft:large_fern": { source: "grass" },
@@ -3942,7 +4011,7 @@ var BlockTints = class {
3942
4011
  };
3943
4012
 
3944
4013
  // src/model/scene/ModelObject.ts
3945
- import { BoxGeometry as BoxGeometry5, BufferAttribute as BufferAttribute4, Color as Color3, Euler as Euler5, Matrix4 as Matrix44 } from "three";
4014
+ import { BoxGeometry as BoxGeometry5, BufferAttribute as BufferAttribute4, Color as Color3, Euler as Euler6, Matrix4 as Matrix44 } from "three";
3946
4015
 
3947
4016
  // src/three/BufferGeometryUtils.js
3948
4017
  import {
@@ -4533,7 +4602,7 @@ var _ModelObject = class _ModelObject extends SceneObject {
4533
4602
  elGeo.clearGroups();
4534
4603
  }
4535
4604
  if (this.options.uvLockRotation) {
4536
- UVMapper.lockUvs(elGeo, el.faces, this.atlas, new Euler5(...this.options.uvLockRotation));
4605
+ UVMapper.lockUvs(elGeo, el.faces, this.atlas, new Euler6(...this.options.uvLockRotation));
4537
4606
  }
4538
4607
  if (this.options.tints) {
4539
4608
  const colors = new Float32Array(elGeo.getAttribute("position").count * 3).fill(1);
@@ -4631,18 +4700,7 @@ function isModelObject(obj) {
4631
4700
 
4632
4701
  // src/model/block/scene/BlockObject.ts
4633
4702
  var import_ts_deepmerge6 = __toESM(require_dist());
4634
- import { Euler as Euler6, Matrix4 as Matrix45, Quaternion as Quaternion4, Vector3 as Vector38 } from "three";
4635
- var p10 = prefix("BlockObject");
4636
- function matchesCondition(condition, state) {
4637
- return Object.entries(condition).every(([key, value]) => {
4638
- if (Array.isArray(value)) {
4639
- if (key === "OR") return value.some((child) => matchesCondition(child, state));
4640
- if (key === "AND") return value.every((child) => matchesCondition(child, state));
4641
- return false;
4642
- }
4643
- return typeof value === "string" && state[key] !== void 0 && value.split("|").includes(`${state[key]}`);
4644
- });
4645
- }
4703
+ import { Euler as Euler7, Matrix4 as Matrix45, Quaternion as Quaternion4, Vector3 as Vector38 } from "three";
4646
4704
  var _BlockObject = class _BlockObject extends SceneObject {
4647
4705
  constructor(blockState, options) {
4648
4706
  super(options);
@@ -4661,22 +4719,7 @@ var _BlockObject = class _BlockObject extends SceneObject {
4661
4719
  }
4662
4720
  async init() {
4663
4721
  if (this.options.applyDefaultState) {
4664
- const defaultState = this.blockState.key ? await BlockStates.getDefaultState(this.blockState.key) : void 0;
4665
- if (defaultState && Object.keys(defaultState).length > 0) {
4666
- const state = {};
4667
- for (let k in defaultState) {
4668
- state[k] = defaultState[k].default;
4669
- }
4670
- this._setState(state);
4671
- } else {
4672
- if (this.blockState.variants) {
4673
- this._setState(Object.keys(this.blockState.variants)[0]);
4674
- } else if (this.blockState.multipart) {
4675
- const condition = this.blockState.multipart.map((part) => part.when).find((when) => when !== void 0 && Object.values(when).every((value) => typeof value === "string"));
4676
- const state = Object.fromEntries(Object.entries(condition ?? {}).map(([key, value]) => [key, value.split("|")[0]]));
4677
- this._setState(state);
4678
- }
4679
- }
4722
+ this._setState(await BlockStateResolver.defaults(this.blockState));
4680
4723
  }
4681
4724
  if (this.options.initialState !== void 0) this._setState(this.options.initialState);
4682
4725
  await this.recreateModels();
@@ -4719,7 +4762,7 @@ var _BlockObject = class _BlockObject extends SceneObject {
4719
4762
  const scale = new Vector38();
4720
4763
  this.getModelMatrix(model).decompose(position, rotation, scale);
4721
4764
  if (position.distanceToSquared(this.position) > 1e-10 || scale.distanceToSquared(new Vector38(1, 1, 1)) > 1e-10) return 0;
4722
- return ModelCulling.toLocalMask(worldMask, new Euler6().setFromQuaternion(rotation));
4765
+ return ModelCulling.toLocalMask(worldMask, new Euler7().setFromQuaternion(rotation));
4723
4766
  }
4724
4767
  async setCullMask(worldMask) {
4725
4768
  worldMask &= 63;
@@ -4767,39 +4810,7 @@ var _BlockObject = class _BlockObject extends SceneObject {
4767
4810
  return ref;
4768
4811
  }
4769
4812
  async mapStateToVariant(state) {
4770
- const out = [];
4771
- if (this.blockState.variants) {
4772
- if (Object.keys(this.blockState.variants).length === 1 && "" in this.blockState.variants) {
4773
- out.push(this.getSingleVariant(this.blockState.variants[""]));
4774
- } else {
4775
- for (let variantKey in this.blockState.variants) {
4776
- const split = variantKey.split(",");
4777
- let matches = true;
4778
- for (let s of split) {
4779
- const [k, v] = s.split("=");
4780
- if (`${state[k]}` !== `${v}`) {
4781
- matches = false;
4782
- break;
4783
- }
4784
- }
4785
- if (matches) {
4786
- const variants = this.blockState.variants[variantKey];
4787
- out.push(this.getSingleVariant(variants));
4788
- }
4789
- }
4790
- }
4791
- } else if (this.blockState.multipart) {
4792
- for (let part of this.blockState.multipart) {
4793
- if (!part.apply) {
4794
- console.debug(p10, "Missing apply for blockState part", part);
4795
- continue;
4796
- }
4797
- if (!part.when || matchesCondition(part.when, state)) {
4798
- out.push(this.getSingleVariant(part.apply));
4799
- }
4800
- }
4801
- }
4802
- return out;
4813
+ return BlockStateResolver.select(this.blockState, state, (variants) => this.getSingleVariant(variants));
4803
4814
  }
4804
4815
  async recreateModels() {
4805
4816
  this.clearModels();
@@ -4810,21 +4821,7 @@ var _BlockObject = class _BlockObject extends SceneObject {
4810
4821
  }
4811
4822
  }
4812
4823
  getSingleVariant(variants) {
4813
- if (!Array.isArray(variants)) return variants;
4814
- if (!variants.length) throw new MineRenderError("Blockstate variant arrays must not be empty");
4815
- const total = variants.reduce((sum, variant) => {
4816
- const weight = variant.weight ?? 1;
4817
- if (!Number.isInteger(weight) || weight < 1) {
4818
- throw new MineRenderError(`Invalid blockstate variant weight: ${weight}`);
4819
- }
4820
- return sum + weight;
4821
- }, 0);
4822
- let choice = Math.random() * total;
4823
- for (const variant of variants) {
4824
- choice -= variant.weight ?? 1;
4825
- if (choice < 0) return variant;
4826
- }
4827
- return variants[variants.length - 1];
4824
+ return BlockStateResolver.choose(variants);
4828
4825
  }
4829
4826
  // @deprecated
4830
4827
  async createAVariant(variants, instanceInfo) {
@@ -4832,13 +4829,7 @@ var _BlockObject = class _BlockObject extends SceneObject {
4832
4829
  await this.createVariant(variant);
4833
4830
  }
4834
4831
  async createVariant(variant) {
4835
- const rotation = new Euler6();
4836
- if (typeof variant.x !== "undefined") {
4837
- rotation.x = toRadians(clampRotationDegrees(variant.x));
4838
- }
4839
- if (typeof variant.y !== "undefined") {
4840
- rotation.y = toRadians(clampRotationDegrees(typeof variant.x !== "undefined" ? variant.y : 360 - variant.y));
4841
- }
4832
+ const rotation = BlockStateResolver.rotation(variant);
4842
4833
  const modelKey = AssetKey.parse("models", variant.model);
4843
4834
  modelKey.root = this.blockState.key?.root;
4844
4835
  const model = await Models.getMerged(modelKey);
@@ -9343,32 +9334,121 @@ var AnvilParser = class {
9343
9334
  };
9344
9335
 
9345
9336
  // src/world/Chunk.ts
9337
+ import { Vector3 as Vector313 } from "three";
9338
+
9339
+ // src/world/MineRenderWorld.ts
9346
9340
  import { Vector3 as Vector312 } from "three";
9347
9341
 
9342
+ // src/world/SectionModels.ts
9343
+ import { Matrix4 as Matrix46, Vector3 as Vector311 } from "three";
9344
+ var SectionModel = class extends ModelObject {
9345
+ applyTextures() {
9346
+ }
9347
+ };
9348
+ function cached(map, key, load) {
9349
+ let value = map.get(key);
9350
+ if (!value) {
9351
+ value = load().catch((error) => {
9352
+ map.delete(key);
9353
+ throw error;
9354
+ });
9355
+ map.set(key, value);
9356
+ }
9357
+ return value;
9358
+ }
9359
+ var SectionModels = class {
9360
+ constructor(maxAtlasSize = 2048) {
9361
+ this.maxAtlasSize = maxAtlasSize;
9362
+ this.states = /* @__PURE__ */ new WeakMap();
9363
+ this.models = /* @__PURE__ */ new WeakMap();
9364
+ this.templates = /* @__PURE__ */ new Set();
9365
+ if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("maxAtlasSize must be a positive integer");
9366
+ }
9367
+ async get(blockState, properties = {}) {
9368
+ if (!blockState.variants || blockState.multipart) return void 0;
9369
+ let states = this.states.get(blockState);
9370
+ if (!states) this.states.set(blockState, states = /* @__PURE__ */ new Map());
9371
+ const key = JSON.stringify(Object.entries(properties).sort(([a], [b]) => a.localeCompare(b)));
9372
+ const prepared = await cached(states, key, () => this.prepare(blockState, properties));
9373
+ if (!prepared) return void 0;
9374
+ return prepared.templates.get(BlockStateResolver.choose(prepared.variants));
9375
+ }
9376
+ async prepare(blockState, properties) {
9377
+ const state = { ...await BlockStateResolver.defaults(blockState), ...properties };
9378
+ const groups = BlockStateResolver.matching(blockState, state);
9379
+ if (groups.length !== 1) return void 0;
9380
+ const variants = groups[0];
9381
+ const choices = Array.isArray(variants) ? variants : [variants];
9382
+ if (!choices.length) return void 0;
9383
+ const templates = /* @__PURE__ */ new Map();
9384
+ for (const variant of choices) {
9385
+ const model = await Models.getMerged(AssetKey.parse("models", variant.model));
9386
+ if (!model) return void 0;
9387
+ const atlas = await UVMapper.getAtlas(model);
9388
+ const rotation = BlockStateResolver.rotation(variant);
9389
+ 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;
9390
+ const tints = await BlockTints.get(blockState.key, state, model);
9391
+ let models = this.models.get(model);
9392
+ if (!models) this.models.set(model, models = /* @__PURE__ */ new Map());
9393
+ const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints]);
9394
+ const template = await cached(models, key, async () => {
9395
+ const object = new SectionModel(model, {
9396
+ instanceMeshes: false,
9397
+ mergeMeshes: false,
9398
+ tints,
9399
+ uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0) ? [rotation.x, rotation.y, rotation.z] : void 0
9400
+ });
9401
+ await object.init();
9402
+ const geometry = object.children[0].geometry;
9403
+ geometry.applyMatrix4(new Matrix46().makeRotationFromEuler(rotation));
9404
+ object.clear();
9405
+ const cullFaces = CUBE_FACES.map((face) => {
9406
+ const local = CUBE_FACES.findIndex((name) => name === atlas.model.elements[0].faces[face]?.cullface);
9407
+ if (local < 0) return 0;
9408
+ const normal = new Vector311(...CUBE_FACE_OFFSETS[local]).applyEuler(rotation);
9409
+ 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);
9410
+ return 1 << world;
9411
+ });
9412
+ const template2 = { geometry, atlas, cullFaces };
9413
+ this.templates.add(template2);
9414
+ return template2;
9415
+ });
9416
+ templates.set(variant, template);
9417
+ }
9418
+ return { variants, templates };
9419
+ }
9420
+ clear() {
9421
+ for (const template of this.templates) template.geometry.dispose();
9422
+ this.templates.clear();
9423
+ this.states = /* @__PURE__ */ new WeakMap();
9424
+ this.models = /* @__PURE__ */ new WeakMap();
9425
+ }
9426
+ };
9427
+
9348
9428
  // src/world/MineRenderWorld.ts
9349
- import { Vector3 as Vector311 } from "three";
9350
9429
  var MineRenderWorld = class {
9351
- constructor(scene) {
9430
+ constructor(scene, options = {}) {
9352
9431
  this._chunks = /* @__PURE__ */ new Map();
9353
9432
  this.pendingCulling = /* @__PURE__ */ new Map();
9354
9433
  this.scene = scene;
9434
+ if (options.sectionMeshing) this.sectionModels = new SectionModels(options.maxAtlasSize);
9355
9435
  }
9356
9436
  getBlockAt(posOrX, y, z) {
9357
9437
  if (typeof posOrX == "number") {
9358
- return this.getBlockAt(new Vector311(posOrX, y, z));
9438
+ return this.getBlockAt(new Vector312(posOrX, y, z));
9359
9439
  }
9360
9440
  if (isTripleArray(posOrX)) {
9361
- return this.getBlockAt(new Vector311(posOrX[0], posOrX[1], posOrX[2]));
9441
+ return this.getBlockAt(new Vector312(posOrX[0], posOrX[1], posOrX[2]));
9362
9442
  }
9363
9443
  this.validatePosBounds(posOrX);
9364
9444
  return this.getChunkAt(posOrX)?.getBlockAt(posOrX);
9365
9445
  }
9366
9446
  async setBlockAt(posOrX, yOrBlock, z, block) {
9367
9447
  if (typeof posOrX == "number") {
9368
- return this.setBlockAt(new Vector311(posOrX, yOrBlock, z), block);
9448
+ return this.setBlockAt(new Vector312(posOrX, yOrBlock, z), block);
9369
9449
  }
9370
9450
  if (isTripleArray(posOrX)) {
9371
- return this.setBlockAt(new Vector311(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
9451
+ return this.setBlockAt(new Vector312(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
9372
9452
  }
9373
9453
  return this.placeBlock(posOrX, yOrBlock);
9374
9454
  }
@@ -9389,7 +9469,7 @@ var MineRenderWorld = class {
9389
9469
  const collect = async (positions2) => {
9390
9470
  for (const pos of positions2) changes.set(pos.toArray().join(","), pos);
9391
9471
  };
9392
- const place = (block) => this.placeBlock(new Vector311(...block.position), block, collect);
9472
+ const place = (block) => this.placeBlock(new Vector312(...block.position), block, collect);
9393
9473
  const keys = /* @__PURE__ */ new Map();
9394
9474
  for (const block of multiblock.blocks) {
9395
9475
  if (Chunk.isAir(block)) continue;
@@ -9461,12 +9541,13 @@ var MineRenderWorld = class {
9461
9541
  for (const chunk of chunks) {
9462
9542
  await chunk.dispose();
9463
9543
  }
9544
+ this.sectionModels?.clear();
9464
9545
  }
9465
9546
  updateCulling(positions) {
9466
9547
  for (const pos of positions) {
9467
9548
  this.pendingCulling.set(pos.toArray().join(","), pos.clone());
9468
9549
  for (const offset of CUBE_FACE_OFFSETS) {
9469
- const neighbor = pos.clone().add(new Vector311(...offset));
9550
+ const neighbor = pos.clone().add(new Vector312(...offset));
9470
9551
  this.pendingCulling.set(neighbor.toArray().join(","), neighbor);
9471
9552
  }
9472
9553
  }
@@ -9475,17 +9556,22 @@ var MineRenderWorld = class {
9475
9556
  while (this.pendingCulling.size) {
9476
9557
  const batch = [...this.pendingCulling.values()];
9477
9558
  this.pendingCulling.clear();
9559
+ const changed = /* @__PURE__ */ new Set();
9478
9560
  for (const pos of batch) {
9479
- const block = this.getBlockAt(pos)?.object;
9480
- if (!block) continue;
9561
+ const chunk = this.getChunkAt(pos);
9562
+ if (!chunk) continue;
9563
+ changed.add(chunk);
9564
+ if (!chunk.getBlockAt(pos)) continue;
9481
9565
  let mask = 0;
9482
9566
  for (const [face, offset] of CUBE_FACE_OFFSETS.entries()) {
9483
- if (this.getBlockAt(pos.clone().add(new Vector311(...offset)))?.object.isOccluding) {
9567
+ const neighbor = pos.clone().add(new Vector312(...offset));
9568
+ if (this.getChunkAt(neighbor)?.isOccludingAt(neighbor)) {
9484
9569
  mask |= 1 << face;
9485
9570
  }
9486
9571
  }
9487
- await block.setCullMask(mask);
9572
+ await chunk.setCullMaskAt(pos, mask);
9488
9573
  }
9574
+ for (const chunk of changed) chunk.rebuildSectionMesh();
9489
9575
  }
9490
9576
  } finally {
9491
9577
  this.culling = void 0;
@@ -9501,7 +9587,8 @@ var MineRenderWorld = class {
9501
9587
  Math.floor(pos.x / 16),
9502
9588
  Math.floor(pos.y / 16),
9503
9589
  Math.floor(pos.z / 16),
9504
- (positions) => this.updateCulling(positions)
9590
+ (positions) => this.updateCulling(positions),
9591
+ this.sectionModels
9505
9592
  );
9506
9593
  this._chunks.set(key, chunk);
9507
9594
  }
@@ -9511,14 +9598,14 @@ var MineRenderWorld = class {
9511
9598
  return this._chunks.get(this.worldPosToChunkKey(pos));
9512
9599
  }
9513
9600
  static worldToScenePosition(pos) {
9514
- return new Vector311(
9601
+ return new Vector312(
9515
9602
  pos.x * 16,
9516
9603
  pos.y * 16,
9517
9604
  pos.z * 16
9518
9605
  );
9519
9606
  }
9520
9607
  static sceneToWorldPosition(pos) {
9521
- return new Vector311(
9608
+ return new Vector312(
9522
9609
  pos.x / 16,
9523
9610
  pos.y / 16,
9524
9611
  pos.z / 16
@@ -9537,12 +9624,118 @@ var MineRenderWorld = class {
9537
9624
  }
9538
9625
  };
9539
9626
 
9627
+ // src/world/SectionMesh.ts
9628
+ import { BufferGeometry as BufferGeometry2, Float32BufferAttribute as Float32BufferAttribute4, Group, Mesh as Mesh7 } from "three";
9629
+ var SectionMesh = class _SectionMesh extends Group {
9630
+ constructor() {
9631
+ super(...arguments);
9632
+ this.ownedMeshes = [];
9633
+ }
9634
+ static build(entries, maxAtlasSize = 2048) {
9635
+ if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
9636
+ const visible = entries.filter((entry) => entry.template.cullFaces.some((direction) => !(entry.cullMask & direction)));
9637
+ const atlases = [...new Set(visible.map((entry) => entry.template.atlas))].sort((a, b) => b.image.height - a.image.height);
9638
+ const pages = [];
9639
+ const locations = /* @__PURE__ */ new Map();
9640
+ for (const atlas of atlases) {
9641
+ const { width, height } = atlas.image;
9642
+ if (width > maxAtlasSize || height > maxAtlasSize) throw new RangeError("Model atlas exceeds the section atlas size limit");
9643
+ let selected;
9644
+ for (const page of pages) {
9645
+ const nextRow = page.x + width > maxAtlasSize;
9646
+ if ((nextRow ? page.y + page.rowHeight : page.y) + height > maxAtlasSize) continue;
9647
+ if (nextRow) {
9648
+ page.y += page.rowHeight;
9649
+ page.x = 0;
9650
+ page.rowHeight = 0;
9651
+ }
9652
+ selected = page;
9653
+ break;
9654
+ }
9655
+ if (!selected) {
9656
+ selected = { placements: [], entries: [], x: 0, y: 0, rowHeight: 0, width: 0, height: 0 };
9657
+ pages.push(selected);
9658
+ }
9659
+ const placement = { atlas, x: selected.x, y: selected.y };
9660
+ selected.placements.push(placement);
9661
+ selected.x += width;
9662
+ selected.rowHeight = Math.max(selected.rowHeight, height);
9663
+ selected.width = Math.max(selected.width, selected.x);
9664
+ selected.height = Math.max(selected.height, selected.y + height);
9665
+ locations.set(atlas, { page: selected, placement });
9666
+ }
9667
+ for (const entry of visible) locations.get(entry.template.atlas).page.entries.push(entry);
9668
+ const section = new _SectionMesh();
9669
+ for (const page of pages) {
9670
+ const canvas = createCanvas(page.width, page.height);
9671
+ const context = canvas.getContext("2d");
9672
+ for (const { atlas, x, y } of page.placements) {
9673
+ context.drawImage(atlas.image.canvas, x, y);
9674
+ }
9675
+ const positions = [], normals = [], uvs = [], colors = [], indices = [];
9676
+ for (const { index, template, cullMask } of page.entries) {
9677
+ const position = template.geometry.getAttribute("position");
9678
+ const normal = template.geometry.getAttribute("normal");
9679
+ const uv = template.geometry.getAttribute("uv");
9680
+ const color = template.geometry.getAttribute("color");
9681
+ const sourceIndices = template.geometry.getIndex();
9682
+ const placement = locations.get(template.atlas).placement;
9683
+ const offsetX = index % 16 * 16;
9684
+ const offsetY = Math.floor(index / 256) * 16;
9685
+ const offsetZ = Math.floor(index / 16) % 16 * 16;
9686
+ for (let face = 0; face < 6; face++) {
9687
+ if (cullMask & template.cullFaces[face]) continue;
9688
+ const start = positions.length / 3;
9689
+ for (let corner = 0; corner < 4; corner++) {
9690
+ const vertex = face * 4 + corner;
9691
+ positions.push(position.getX(vertex) + offsetX, position.getY(vertex) + offsetY, position.getZ(vertex) + offsetZ);
9692
+ normals.push(normal.getX(vertex), normal.getY(vertex), normal.getZ(vertex));
9693
+ uvs.push(
9694
+ (placement.x + uv.getX(vertex) * template.atlas.image.width) / page.width,
9695
+ 1 - (placement.y + (1 - uv.getY(vertex)) * template.atlas.image.height) / page.height
9696
+ );
9697
+ colors.push(color?.getX(vertex) ?? 1, color?.getY(vertex) ?? 1, color?.getZ(vertex) ?? 1);
9698
+ }
9699
+ for (let corner = 0; corner < 6; corner++) indices.push(start + sourceIndices.getX(face * 6 + corner) - face * 4);
9700
+ }
9701
+ }
9702
+ const geometry = new BufferGeometry2();
9703
+ geometry.setAttribute("position", new Float32BufferAttribute4(positions, 3));
9704
+ geometry.setAttribute("normal", new Float32BufferAttribute4(normals, 3));
9705
+ geometry.setAttribute("uv", new Float32BufferAttribute4(uvs, 2));
9706
+ geometry.setAttribute("color", new Float32BufferAttribute4(colors, 3));
9707
+ geometry.setIndex(indices);
9708
+ geometry.computeBoundingBox();
9709
+ geometry.computeBoundingSphere();
9710
+ const material = Materials.createShadedCanvasMaterial(canvas, false);
9711
+ material.vertexColors = true;
9712
+ const texture = material.uniforms?.map?.value ?? material.map;
9713
+ const mesh = new Mesh7(geometry, material);
9714
+ section.add(mesh);
9715
+ section.ownedMeshes.push({ mesh, texture });
9716
+ }
9717
+ return section;
9718
+ }
9719
+ dispose() {
9720
+ for (const { mesh, texture } of this.ownedMeshes.splice(0)) {
9721
+ mesh.removeFromParent();
9722
+ mesh.geometry.dispose();
9723
+ mesh.material.dispose();
9724
+ texture?.dispose();
9725
+ }
9726
+ this.removeFromParent();
9727
+ }
9728
+ };
9729
+
9540
9730
  // src/world/Chunk.ts
9541
9731
  var Chunk = class _Chunk {
9542
- constructor(scene, x, y, z, onBlocksChanged) {
9732
+ constructor(scene, x, y, z, onBlocksChanged, sectionModels) {
9543
9733
  this.onBlocksChanged = onBlocksChanged;
9734
+ this.sectionModels = sectionModels;
9544
9735
  this.data = new ChunkData();
9545
9736
  this.renderedBlocks = /* @__PURE__ */ new Map();
9737
+ this.sectionBlocks = /* @__PURE__ */ new Map();
9738
+ this.meshDirty = false;
9546
9739
  this.scene = scene;
9547
9740
  this.x = x;
9548
9741
  this.y = y;
@@ -9550,10 +9743,10 @@ var Chunk = class _Chunk {
9550
9743
  }
9551
9744
  getBlockAt(posOrX, y, z) {
9552
9745
  if (typeof posOrX == "number") {
9553
- return this.getBlockAt(new Vector312(posOrX, y, z));
9746
+ return this.getBlockAt(new Vector313(posOrX, y, z));
9554
9747
  }
9555
9748
  if (isTripleArray(posOrX)) {
9556
- return this.getBlockAt(new Vector312(posOrX[0], posOrX[1], posOrX[2]));
9749
+ return this.getBlockAt(new Vector313(posOrX[0], posOrX[1], posOrX[2]));
9557
9750
  }
9558
9751
  const pos = this.worldPosToChunkPos(posOrX);
9559
9752
  const index = _Chunk.chunkPosToBlockIndex(pos);
@@ -9561,10 +9754,10 @@ var Chunk = class _Chunk {
9561
9754
  }
9562
9755
  async setBlockAt(posOrX, yOrBlock, z, block) {
9563
9756
  if (typeof posOrX == "number") {
9564
- return this.setBlockAt(new Vector312(posOrX, yOrBlock, z), block);
9757
+ return this.setBlockAt(new Vector313(posOrX, yOrBlock, z), block);
9565
9758
  }
9566
9759
  if (isTripleArray(posOrX)) {
9567
- return this.setBlockAt(new Vector312(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
9760
+ return this.setBlockAt(new Vector313(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
9568
9761
  }
9569
9762
  const worldPos = posOrX;
9570
9763
  const pos = this.worldPosToChunkPos(worldPos);
@@ -9581,7 +9774,8 @@ var Chunk = class _Chunk {
9581
9774
  const index = _Chunk.chunkPosToBlockIndex(pos);
9582
9775
  this.data.set(index, block);
9583
9776
  const readBlock = this.data.snapshot(index);
9584
- this.renderedBlocks.get(index)?.object.removeFromScene();
9777
+ this.renderedBlocks.get(index)?.object?.removeFromScene();
9778
+ if (this.sectionBlocks.delete(index)) this.meshDirty = true;
9585
9779
  this.renderedBlocks.delete(index);
9586
9780
  let object;
9587
9781
  try {
@@ -9592,13 +9786,19 @@ var Chunk = class _Chunk {
9592
9786
  this.data.set(index, void 0);
9593
9787
  return void 0;
9594
9788
  }
9595
- object = await this.scene.addBlock(blockState, {
9596
- mergeMeshes: true,
9597
- instanceMeshes: true,
9598
- maxInstanceCount: 2e3,
9599
- initialState: stored.properties
9600
- });
9601
- object.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
9789
+ const template = await this.sectionModels?.get(blockState, stored.properties);
9790
+ if (template) {
9791
+ this.sectionBlocks.set(index, { index, template, cullMask: 0 });
9792
+ this.meshDirty = true;
9793
+ } else {
9794
+ object = await this.scene.addBlock(blockState, {
9795
+ mergeMeshes: true,
9796
+ instanceMeshes: true,
9797
+ maxInstanceCount: 2e3,
9798
+ initialState: stored.properties
9799
+ });
9800
+ object.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
9801
+ }
9602
9802
  const info = { get block() {
9603
9803
  return readBlock();
9604
9804
  }, object };
@@ -9612,11 +9812,43 @@ var Chunk = class _Chunk {
9612
9812
  await onBlocksChanged?.([worldPos]);
9613
9813
  }
9614
9814
  }
9815
+ isOccludingAt(pos) {
9816
+ const index = _Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos));
9817
+ return this.sectionBlocks.has(index) || (this.renderedBlocks.get(index)?.object?.isOccluding ?? false);
9818
+ }
9819
+ async setCullMaskAt(pos, mask) {
9820
+ const index = _Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos));
9821
+ const entry = this.sectionBlocks.get(index);
9822
+ if (entry) {
9823
+ if (entry.cullMask !== mask) {
9824
+ entry.cullMask = mask;
9825
+ this.meshDirty = true;
9826
+ }
9827
+ } else {
9828
+ await this.renderedBlocks.get(index)?.object?.setCullMask(mask);
9829
+ }
9830
+ }
9831
+ rebuildSectionMesh() {
9832
+ if (!this.meshDirty) return;
9833
+ const next = this.sectionBlocks.size ? SectionMesh.build([...this.sectionBlocks.values()], this.sectionModels.maxAtlasSize) : void 0;
9834
+ this.sectionMesh?.dispose();
9835
+ this.sectionMesh = next;
9836
+ if (next) {
9837
+ next.position.set(this.x * 256, this.y * 256, this.z * 256);
9838
+ this.scene.add(next);
9839
+ }
9840
+ this.scene.dirty = true;
9841
+ this.meshDirty = false;
9842
+ }
9615
9843
  async clear(onBlocksChanged = this.onBlocksChanged) {
9616
9844
  const positions = [...this.renderedBlocks.keys()].map((index) => this.chunkPosToWorldPos(
9617
- new Vector312(index % 16, Math.floor(index / 256), Math.floor(index / 16) % 16)
9845
+ new Vector313(index % 16, Math.floor(index / 256), Math.floor(index / 16) % 16)
9618
9846
  ));
9619
- for (const info of this.renderedBlocks.values()) info.object.removeFromScene();
9847
+ for (const info of this.renderedBlocks.values()) info.object?.removeFromScene();
9848
+ this.sectionBlocks.clear();
9849
+ this.sectionMesh?.dispose();
9850
+ this.sectionMesh = void 0;
9851
+ this.meshDirty = false;
9620
9852
  this.renderedBlocks.clear();
9621
9853
  this.data.clear();
9622
9854
  await onBlocksChanged?.(positions);
@@ -9634,14 +9866,14 @@ var Chunk = class _Chunk {
9634
9866
  return pos.y * 16 * 16 + pos.z * 16 + pos.x;
9635
9867
  }
9636
9868
  worldPosToChunkPos(pos) {
9637
- return new Vector312(
9869
+ return new Vector313(
9638
9870
  pos.x - this.x * 16,
9639
9871
  pos.y - this.y * 16,
9640
9872
  pos.z - this.z * 16
9641
9873
  );
9642
9874
  }
9643
9875
  chunkPosToWorldPos(pos) {
9644
- return new Vector312(
9876
+ return new Vector313(
9645
9877
  this.x * 16 + pos.x,
9646
9878
  this.y * 16 + pos.y,
9647
9879
  this.z * 16 + pos.z
@@ -9664,6 +9896,7 @@ export {
9664
9896
  BasicAssetKey,
9665
9897
  BatchedExecutor,
9666
9898
  BlockObject,
9899
+ BlockStateResolver,
9667
9900
  BlockStates,
9668
9901
  BlockTints,
9669
9902
  BrowserArchiveProxy,
@@ -9722,6 +9955,8 @@ export {
9722
9955
  SceneStats,
9723
9956
  SceneStatsDisplay,
9724
9957
  SchematicParser,
9958
+ SectionMesh,
9959
+ SectionModels,
9725
9960
  SkinImage,
9726
9961
  SkinObject,
9727
9962
  SkinPart,