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
  };
@@ -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;
@@ -3100,6 +3100,10 @@ var SceneObject = class _SceneObject extends Object3D2 {
3100
3100
  this.setMatrixAt(index, new Matrix43());
3101
3101
  return reference;
3102
3102
  }
3103
+ /** Returns the live reference for a raycast hit on this object's instance mesh. */
3104
+ getInstanceReference(mesh, index) {
3105
+ return mesh === this.instanceMesh ? this.instanceReferences.get(index) : void 0;
3106
+ }
3103
3107
  isInstanceActive(index, reference) {
3104
3108
  return this.instanceReferences.get(index) === reference;
3105
3109
  }
@@ -3182,9 +3186,9 @@ var SceneObject = class _SceneObject extends Object3D2 {
3182
3186
  }
3183
3187
  getRotationAt(index, euler = new Euler3()) {
3184
3188
  if (!this.isInstanced) throw new MineRenderError("Object is not instanced");
3185
- const matrix = this.getMatrixAt(index);
3186
- euler.setFromRotationMatrix(matrix);
3187
- return euler;
3189
+ const rotation = new Quaternion3();
3190
+ this.getMatrixAt(index).decompose(new Vector35(), rotation, new Vector35());
3191
+ return euler.setFromQuaternion(rotation);
3188
3192
  }
3189
3193
  setScaleAt(index, scale) {
3190
3194
  if (!this.isInstanced) throw new MineRenderError("Object is not instanced");
@@ -3434,11 +3438,12 @@ var GuiObject = class extends SceneObject {
3434
3438
  // src/inspector/SceneInspector.ts
3435
3439
  import { Raycaster, Vector2 as Vector22 } from "three";
3436
3440
  var p7 = prefix("SceneInspector");
3437
- var help = "<span>Ctrl+Click to Select<br/></span><br/>";
3441
+ var help = "<span>Ctrl/Cmd+Click to Select<br/></span><br/>";
3438
3442
  var SceneInspector = class {
3439
3443
  constructor(renderer) {
3440
3444
  this.renderer = renderer;
3441
3445
  this.mouse = new Vector22();
3446
+ this.listeners = new AbortController();
3442
3447
  this.objectInfoContainer = document.createElement("div");
3443
3448
  this.objectControlsContainer = document.createElement("div");
3444
3449
  this.raycaster = new Raycaster();
@@ -3448,13 +3453,44 @@ var SceneInspector = class {
3448
3453
  this.objectInfoContainer.classList.add("minerender-inspector", "minerender-object-info");
3449
3454
  this.objectControlsContainer.classList.add("minerender-inspector", "minerender-object-controls");
3450
3455
  this.objectInfoContainer.innerHTML = help;
3451
- document.addEventListener("click", this.onClick.bind(this), {
3452
- passive: true
3453
- });
3456
+ const canvas = this.renderer.renderer.domElement;
3457
+ const options = { signal: this.listeners.signal };
3458
+ canvas.addEventListener("pointerdown", (event) => {
3459
+ this.pointer = event.button === 0 && (event.ctrlKey || event.metaKey) ? event : void 0;
3460
+ }, options);
3461
+ canvas.addEventListener("pointermove", (event) => {
3462
+ if (this.pointer?.pointerId === event.pointerId && Math.hypot(event.clientX - this.pointer.clientX, event.clientY - this.pointer.clientY) > 5) {
3463
+ this.pointer = void 0;
3464
+ }
3465
+ }, options);
3466
+ canvas.addEventListener("pointerup", (event) => {
3467
+ const start = this.pointer;
3468
+ this.pointer = void 0;
3469
+ if (start?.pointerId === event.pointerId && Math.hypot(event.clientX - start.clientX, event.clientY - start.clientY) <= 5) {
3470
+ this.onClick(event);
3471
+ }
3472
+ }, options);
3473
+ canvas.addEventListener("pointercancel", () => {
3474
+ this.pointer = void 0;
3475
+ }, options);
3476
+ canvas.addEventListener("pointerleave", () => {
3477
+ this.pointer = void 0;
3478
+ }, options);
3479
+ canvas.addEventListener("contextmenu", (event) => {
3480
+ if (this.pointer?.ctrlKey) event.preventDefault();
3481
+ }, options);
3482
+ }
3483
+ dispose() {
3484
+ this.listeners.abort();
3485
+ this.pointer = void 0;
3486
+ this.selectedObject = void 0;
3487
+ this.objectInfoContainer.remove();
3488
+ this.objectControlsContainer.remove();
3454
3489
  }
3455
3490
  findMeshChildren(from, out = []) {
3491
+ if (!from.visible) return out;
3456
3492
  for (let c of from.children) {
3457
- if (isMesh(c)) {
3493
+ if (c.visible && isMesh(c)) {
3458
3494
  out.push(c);
3459
3495
  }
3460
3496
  this.findMeshChildren(c, out);
@@ -3462,50 +3498,43 @@ var SceneInspector = class {
3462
3498
  return out;
3463
3499
  }
3464
3500
  onClick(event) {
3465
- if (event.target?.nodeName !== "CANVAS") return;
3466
- if (!event.ctrlKey) return;
3467
- setTimeout(() => {
3468
- this.mouse.x = event.clientX / window.innerWidth * 2 - 1;
3469
- this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
3470
- this.raycaster.setFromCamera(this.mouse, this.renderer.camera);
3471
- let start = Date.now();
3472
- let targets = this.findMeshChildren(this.renderer.scene);
3473
- const intersects = this.raycaster.intersectObjects(targets, false);
3474
- console.debug("Raycast took " + (Date.now() - start) + "ms for " + targets.length + " possible targets with " + intersects.length + " results");
3475
- if (intersects.length > 0) {
3476
- this.handleRaycasterObjects(intersects);
3477
- }
3478
- });
3501
+ const canvas = this.renderer.renderer.domElement;
3502
+ if (event.target !== canvas || event.button !== 0 || !(event.ctrlKey || event.metaKey)) return;
3503
+ const rect = canvas.getBoundingClientRect();
3504
+ const x = event.clientX - rect.left, y = event.clientY - rect.top;
3505
+ if (rect.width <= 0 || rect.height <= 0 || x < 0 || y < 0 || x >= rect.width || y >= rect.height) return;
3506
+ this.mouse.set(x / rect.width * 2 - 1, 1 - y / rect.height * 2);
3507
+ this.renderer.camera.updateWorldMatrix(true, false);
3508
+ this.renderer.scene.updateMatrixWorld(true);
3509
+ this.raycaster.setFromCamera(this.mouse, this.renderer.camera);
3510
+ this.handleRaycasterObjects(this.raycaster.intersectObjects(this.findMeshChildren(this.renderer.scene), false));
3479
3511
  }
3480
3512
  appendTo(el) {
3481
3513
  el.append(this.objectInfoContainer);
3482
3514
  el.append(this.objectControlsContainer);
3483
3515
  }
3484
3516
  handleRaycasterObjects(intersections) {
3485
- console.debug(intersections);
3486
- const firstIntersection = intersections[0];
3487
- if (firstIntersection && firstIntersection.object) {
3488
- const firstObject = firstIntersection.object;
3489
- let targetIntersection = void 0;
3490
- let targetObject = void 0;
3491
- const firstSceneObjectIntersection = intersections.find((i) => i.object && i.object.parent && isSceneObject(i.object.parent));
3492
- if (firstSceneObjectIntersection && firstSceneObjectIntersection.object) {
3493
- targetIntersection = firstSceneObjectIntersection;
3494
- targetObject = firstSceneObjectIntersection.object;
3495
- } else {
3496
- targetIntersection = firstIntersection;
3497
- targetObject = firstObject;
3517
+ const hit = intersections[0];
3518
+ if (!hit) return;
3519
+ const instance = this.getIntersectionInstance(hit);
3520
+ this.selectObject(instance?.instanceable ?? hit.object, hit, instance);
3521
+ }
3522
+ getIntersectionInstance(hit) {
3523
+ if (hit?.instanceId === void 0) return void 0;
3524
+ for (let parent = hit.object.parent; parent; parent = parent.parent) {
3525
+ if (isSceneObject(parent)) {
3526
+ const instance = parent.getInstanceReference(hit.object, hit.instanceId);
3527
+ if (instance) return instance;
3498
3528
  }
3499
- if (!targetIntersection || !targetObject) return;
3500
- this.selectObject(targetObject, targetIntersection);
3501
3529
  }
3530
+ return void 0;
3502
3531
  }
3503
- selectObject(targetObject, targetIntersection) {
3532
+ selectObject(targetObject, targetIntersection, instance = this.getIntersectionInstance(targetIntersection)) {
3504
3533
  this.selectedObject = targetObject;
3505
3534
  console.log(p7, "selected", targetObject);
3506
- this.redraw(targetObject, targetIntersection);
3535
+ this.redraw(targetObject, targetIntersection, instance);
3507
3536
  }
3508
- redraw(targetObject, targetIntersection) {
3537
+ redraw(targetObject, targetIntersection, instance) {
3509
3538
  this.objectInfoContainer.innerHTML = help;
3510
3539
  if (targetIntersection) {
3511
3540
  this.addInfoLine("Distance", "D", targetIntersection.distance);
@@ -3518,8 +3547,8 @@ var SceneInspector = class {
3518
3547
  if (isSceneObject(targetObject.parent)) {
3519
3548
  }
3520
3549
  }
3521
- if ("atlas" in targetObject) {
3522
- const atlas = targetObject["atlas"];
3550
+ const atlas = "atlas" in targetObject ? targetObject["atlas"] : void 0;
3551
+ if (atlas?.image.canvas) {
3523
3552
  const img = atlas.image.canvas;
3524
3553
  img.style.width = "100px";
3525
3554
  img.style.height = "100px";
@@ -3528,28 +3557,31 @@ var SceneInspector = class {
3528
3557
  this.objectInfoContainer.prepend(img);
3529
3558
  }
3530
3559
  this.objectControlsContainer.innerHTML = "";
3531
- this.addControls(targetObject, targetIntersection);
3560
+ this.addControls(targetObject, targetIntersection, instance);
3532
3561
  }
3533
- addControls(object, intersection) {
3562
+ addControls(object, intersection, instance) {
3534
3563
  const container = document.createElement("div");
3535
3564
  container.append(this.separator("Select Parent/Child"));
3536
3565
  if (object.parent) {
3537
3566
  container.append(this.buttonControl("Select Parent " + object.parent.constructor.name + " " + object.parent.name, "P", () => {
3538
- this.selectObject(object.parent, intersection);
3567
+ this.selectObject(object.parent, intersection, instance);
3539
3568
  }));
3540
3569
  }
3541
3570
  if (object.children.length > 0) {
3542
3571
  let i = 1;
3543
3572
  for (let child of object.children) {
3544
3573
  container.append(this.buttonControl("Select Child " + child.constructor.name + " " + child.name, "C" + i++, () => {
3545
- this.selectObject(child, intersection);
3574
+ this.selectObject(child, intersection, instance);
3546
3575
  }));
3547
3576
  }
3548
3577
  }
3549
3578
  container.append(this.separator());
3550
- container.append(this.toggleControl("Visibility", "V", object.visible, (v) => object.visible = v));
3579
+ const selectedInstance = instance && (object === instance.instanceable || object === intersection?.object) ? instance : void 0;
3580
+ if (!selectedInstance) {
3581
+ container.append(this.toggleControl("Visibility", "V", object.visible, (v) => object.visible = v));
3582
+ }
3551
3583
  container.append(this.separator("Mesh"));
3552
- this.addObjectControls(object, intersection, container);
3584
+ this.addObjectControls(object, intersection, container, selectedInstance);
3553
3585
  container.append(this.separator());
3554
3586
  this.objectControlsContainer.append(container);
3555
3587
  }
@@ -3561,78 +3593,26 @@ var SceneInspector = class {
3561
3593
  this.objectInfoContainer.append(document.createElement("br"));
3562
3594
  return el;
3563
3595
  }
3564
- addObjectControls(target, intersection, container) {
3596
+ addObjectControls(target, intersection, container, instance) {
3565
3597
  const posRange = 16 * 16;
3566
3598
  const rotRange = 360;
3567
3599
  const scaleRange = 4;
3568
- if (typeof intersection !== "undefined" && typeof intersection.instanceId !== "undefined" && isSceneObject(target) && target.isInstanced) {
3569
- const scObj = target;
3570
- container.append(this.separator("Position"));
3571
- let pos = scObj.getPositionAt(intersection.instanceId);
3572
- container.append(this.rangeControl("X Position", "X", -posRange + pos.x, posRange + pos.x, pos.x, 1, (v) => {
3573
- pos = scObj.getPositionAt(intersection.instanceId);
3574
- pos.x = v;
3575
- scObj.setPositionAt(intersection.instanceId, pos);
3576
- }));
3577
- container.append(this.rangeControl("Y Position", "Y", -posRange + pos.y, posRange + pos.y, pos.y, 1, (v) => {
3578
- pos = scObj.getPositionAt(intersection.instanceId);
3579
- pos.y = v;
3580
- scObj.setPositionAt(intersection.instanceId, pos);
3581
- }));
3582
- container.append(this.rangeControl("Z Position", "Z", -posRange + pos.z, posRange + pos.z, pos.z, 1, (v) => {
3583
- pos = scObj.getPositionAt(intersection.instanceId);
3584
- pos.z = v;
3585
- scObj.setPositionAt(intersection.instanceId, pos);
3586
- }));
3587
- container.append(this.separator("Rotation"));
3588
- let rot = scObj.getRotationAt(intersection.instanceId);
3589
- container.append(this.rangeControl("X Rotation", "X", 0, rotRange, Math.round(toDegrees(rot.x)), 1, (v) => {
3590
- rot = scObj.getRotationAt(intersection.instanceId);
3591
- rot.x = toRadians(v);
3592
- scObj.setRotationAt(intersection.instanceId, rot);
3593
- }));
3594
- container.append(this.rangeControl("Y Rotation", "Y", 0, rotRange, Math.round(toDegrees(rot.y)), 1, (v) => {
3595
- rot = scObj.getRotationAt(intersection.instanceId);
3596
- rot.y = toRadians(v);
3597
- scObj.setRotationAt(intersection.instanceId, rot);
3598
- }));
3599
- container.append(this.rangeControl("Z Rotation", "Z", 0, rotRange, Math.round(toDegrees(rot.z)), 1, (v) => {
3600
- rot = scObj.getRotationAt(intersection.instanceId);
3601
- rot.z = toRadians(v);
3602
- scObj.setRotationAt(intersection.instanceId, rot);
3603
- }));
3604
- container.append(this.separator("Scale"));
3605
- let scl = scObj.getScale();
3606
- container.append(this.rangeControl("X Scale", "X", 0, scaleRange, scl.x, 0.1, (v) => {
3607
- scl = scObj.getScaleAt(intersection.instanceId);
3608
- scl.x = v;
3609
- scObj.setScaleAt(intersection.instanceId, scl);
3610
- }));
3611
- container.append(this.rangeControl("Y Scale", "Y", 0, scaleRange, scl.y, 0.1, (v) => {
3612
- scl = scObj.getScaleAt(intersection.instanceId);
3613
- scl.y = v;
3614
- scObj.setScaleAt(intersection.instanceId, scl);
3615
- }));
3616
- container.append(this.rangeControl("Z Scale", "Z", 0, scaleRange, scl.z, 0.1, (v) => {
3617
- scl = scObj.getScaleAt(intersection.instanceId);
3618
- scl.z = v;
3619
- scObj.setScaleAt(intersection.instanceId, scl);
3620
- }));
3621
- } else if (isTransformable(target)) {
3622
- const parent = target;
3600
+ const transform = instance ?? (isTransformable(target) ? target : void 0);
3601
+ if (transform) {
3602
+ const parent = transform;
3623
3603
  container.append(this.separator("Position"));
3624
3604
  let pos = parent.getPosition();
3625
- container.append(this.rangeControl("X Position", "X", -posRange, posRange, pos.x, 1, (v) => {
3605
+ container.append(this.rangeControl("X Position", "X", pos.x - posRange, pos.x + posRange, pos.x, 1, (v) => {
3626
3606
  pos = parent.getPosition();
3627
3607
  pos.x = v;
3628
3608
  parent.setPosition(pos);
3629
3609
  }));
3630
- container.append(this.rangeControl("Y Position", "Y", -posRange, posRange, pos.y, 1, (v) => {
3610
+ container.append(this.rangeControl("Y Position", "Y", pos.y - posRange, pos.y + posRange, pos.y, 1, (v) => {
3631
3611
  pos = parent.getPosition();
3632
3612
  pos.y = v;
3633
3613
  parent.setPosition(pos);
3634
3614
  }));
3635
- container.append(this.rangeControl("Z Position", "Z", -posRange, posRange, pos.z, 1, (v) => {
3615
+ container.append(this.rangeControl("Z Position", "Z", pos.z - posRange, pos.z + posRange, pos.z, 1, (v) => {
3636
3616
  pos = parent.getPosition();
3637
3617
  pos.z = v;
3638
3618
  parent.setPosition(pos);
@@ -3721,6 +3701,7 @@ var SceneInspector = class {
3721
3701
  toggle.checked = val;
3722
3702
  toggle.addEventListener("change", (e) => {
3723
3703
  change(toggle.checked);
3704
+ this.renderer.scene.dirty = true;
3724
3705
  });
3725
3706
  label.append(toggle);
3726
3707
  label.append(document.createElement("br"));
@@ -3733,7 +3714,6 @@ var SceneInspector = class {
3733
3714
  label.setAttribute("title", name);
3734
3715
  labelText.style.width = "18%";
3735
3716
  labelText.style.display = "inline-block";
3736
- labelText.innerText = `${id} (${val})`;
3737
3717
  const range = document.createElement("input");
3738
3718
  range.setAttribute("type", "range");
3739
3719
  range.style.width = "80%";
@@ -3741,8 +3721,10 @@ var SceneInspector = class {
3741
3721
  range.min = `${min}`;
3742
3722
  range.step = `${step}`;
3743
3723
  range.value = `${val}`;
3724
+ labelText.innerText = `${id} (${range.value})`;
3744
3725
  const onChange = () => {
3745
3726
  change(parseFloat(range.value));
3727
+ this.renderer.scene.dirty = true;
3746
3728
  labelText.innerText = `${id} (${range.value})`;
3747
3729
  };
3748
3730
  range.addEventListener("input", onChange);
@@ -3902,6 +3884,93 @@ var ModelCulling = class {
3902
3884
  }
3903
3885
  };
3904
3886
 
3887
+ // src/model/block/BlockStateResolver.ts
3888
+ import { Euler as Euler5 } from "three";
3889
+ function matchesCondition(condition, state) {
3890
+ return Object.entries(condition).every(([key, value]) => {
3891
+ if (Array.isArray(value)) {
3892
+ if (key === "OR") return value.some((child) => matchesCondition(child, state));
3893
+ if (key === "AND") return value.every((child) => matchesCondition(child, state));
3894
+ return false;
3895
+ }
3896
+ return typeof value === "string" && state[key] !== void 0 && value.split("|").includes(`${state[key]}`);
3897
+ });
3898
+ }
3899
+ var BlockStateResolver = class {
3900
+ static async defaults(blockState) {
3901
+ const defaults = blockState.key ? await BlockStates.getDefaultState(blockState.key) : void 0;
3902
+ const state = {};
3903
+ if (defaults && Object.keys(defaults).length > 0) {
3904
+ for (const key in defaults) state[key] = defaults[key].default;
3905
+ } else if (blockState.variants) {
3906
+ const variant = Object.keys(blockState.variants)[0];
3907
+ if (variant) {
3908
+ for (const property of variant.split(",")) {
3909
+ const [key, value] = property.split("=");
3910
+ state[key] = value;
3911
+ }
3912
+ }
3913
+ } else if (blockState.multipart) {
3914
+ const condition = blockState.multipart.map((part) => part.when).find((when) => when !== void 0 && Object.values(when).every((value) => typeof value === "string"));
3915
+ for (const [key, value] of Object.entries(condition ?? {})) state[key] = value.split("|")[0];
3916
+ }
3917
+ return state;
3918
+ }
3919
+ static select(blockState, state, choose = this.choose) {
3920
+ return this.matching(blockState, state).map(choose);
3921
+ }
3922
+ static matching(blockState, state) {
3923
+ const out = [];
3924
+ if (blockState.variants) {
3925
+ if (Object.keys(blockState.variants).length === 1 && "" in blockState.variants) {
3926
+ out.push(blockState.variants[""]);
3927
+ } else {
3928
+ for (const variantKey in blockState.variants) {
3929
+ const matches = variantKey.split(",").every((property) => {
3930
+ const [key, value] = property.split("=");
3931
+ return `${state[key]}` === `${value}`;
3932
+ });
3933
+ if (matches) out.push(blockState.variants[variantKey]);
3934
+ }
3935
+ }
3936
+ } else if (blockState.multipart) {
3937
+ for (const part of blockState.multipart) {
3938
+ if (part.apply && (!part.when || matchesCondition(part.when, state))) {
3939
+ out.push(part.apply);
3940
+ }
3941
+ }
3942
+ }
3943
+ return out;
3944
+ }
3945
+ static choose(variants) {
3946
+ if (!Array.isArray(variants)) return variants;
3947
+ if (!variants.length) throw new MineRenderError("Blockstate variant arrays must not be empty");
3948
+ const total = variants.reduce((sum, variant) => {
3949
+ const weight = variant.weight ?? 1;
3950
+ if (!Number.isInteger(weight) || weight < 1) {
3951
+ throw new MineRenderError(`Invalid blockstate variant weight: ${weight}`);
3952
+ }
3953
+ return sum + weight;
3954
+ }, 0);
3955
+ let choice = Math.random() * total;
3956
+ for (const variant of variants) {
3957
+ choice -= variant.weight ?? 1;
3958
+ if (choice < 0) return variant;
3959
+ }
3960
+ return variants[variants.length - 1];
3961
+ }
3962
+ static rotation(variant) {
3963
+ const rotation = new Euler5();
3964
+ if (typeof variant.x !== "undefined") {
3965
+ rotation.x = toRadians(clampRotationDegrees(variant.x));
3966
+ }
3967
+ if (typeof variant.y !== "undefined") {
3968
+ rotation.y = toRadians(clampRotationDegrees(typeof variant.x !== "undefined" ? variant.y : 360 - variant.y));
3969
+ }
3970
+ return rotation;
3971
+ }
3972
+ };
3973
+
3905
3974
  // src/model/block/blockTints.json
3906
3975
  var blockTints_default = {
3907
3976
  "minecraft:large_fern": { source: "grass" },
@@ -3988,7 +4057,7 @@ var BlockTints = class {
3988
4057
  };
3989
4058
 
3990
4059
  // src/model/scene/ModelObject.ts
3991
- import { BoxGeometry as BoxGeometry5, BufferAttribute as BufferAttribute4, Color as Color3, Euler as Euler5, Matrix4 as Matrix44 } from "three";
4060
+ import { BoxGeometry as BoxGeometry5, BufferAttribute as BufferAttribute4, Color as Color3, Euler as Euler6, Matrix4 as Matrix44 } from "three";
3992
4061
 
3993
4062
  // src/three/BufferGeometryUtils.js
3994
4063
  import {
@@ -4582,7 +4651,7 @@ var ModelObject = class _ModelObject extends SceneObject {
4582
4651
  elGeo.clearGroups();
4583
4652
  }
4584
4653
  if (this.options.uvLockRotation) {
4585
- UVMapper.lockUvs(elGeo, el.faces, this.atlas, new Euler5(...this.options.uvLockRotation));
4654
+ UVMapper.lockUvs(elGeo, el.faces, this.atlas, new Euler6(...this.options.uvLockRotation));
4586
4655
  }
4587
4656
  if (this.options.tints) {
4588
4657
  const colors = new Float32Array(elGeo.getAttribute("position").count * 3).fill(1);
@@ -4678,18 +4747,7 @@ function isModelObject(obj) {
4678
4747
 
4679
4748
  // src/model/block/scene/BlockObject.ts
4680
4749
  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
- }
4750
+ import { Euler as Euler7, Matrix4 as Matrix45, Quaternion as Quaternion4, Vector3 as Vector38 } from "three";
4693
4751
  var BlockObject = class _BlockObject extends SceneObject {
4694
4752
  constructor(blockState, options) {
4695
4753
  super(options);
@@ -4713,22 +4771,7 @@ var BlockObject = class _BlockObject extends SceneObject {
4713
4771
  }
4714
4772
  async init() {
4715
4773
  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
- }
4774
+ this._setState(await BlockStateResolver.defaults(this.blockState));
4732
4775
  }
4733
4776
  if (this.options.initialState !== void 0) this._setState(this.options.initialState);
4734
4777
  await this.recreateModels();
@@ -4771,7 +4814,7 @@ var BlockObject = class _BlockObject extends SceneObject {
4771
4814
  const scale = new Vector38();
4772
4815
  this.getModelMatrix(model).decompose(position, rotation, scale);
4773
4816
  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));
4817
+ return ModelCulling.toLocalMask(worldMask, new Euler7().setFromQuaternion(rotation));
4775
4818
  }
4776
4819
  async setCullMask(worldMask) {
4777
4820
  worldMask &= 63;
@@ -4819,39 +4862,7 @@ var BlockObject = class _BlockObject extends SceneObject {
4819
4862
  return ref;
4820
4863
  }
4821
4864
  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;
4865
+ return BlockStateResolver.select(this.blockState, state, (variants) => this.getSingleVariant(variants));
4855
4866
  }
4856
4867
  async recreateModels() {
4857
4868
  this.clearModels();
@@ -4862,21 +4873,7 @@ var BlockObject = class _BlockObject extends SceneObject {
4862
4873
  }
4863
4874
  }
4864
4875
  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];
4876
+ return BlockStateResolver.choose(variants);
4880
4877
  }
4881
4878
  // @deprecated
4882
4879
  async createAVariant(variants, instanceInfo) {
@@ -4884,13 +4881,7 @@ var BlockObject = class _BlockObject extends SceneObject {
4884
4881
  await this.createVariant(variant);
4885
4882
  }
4886
4883
  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
- }
4884
+ const rotation = BlockStateResolver.rotation(variant);
4894
4885
  const modelKey = AssetKey.parse("models", variant.model);
4895
4886
  modelKey.root = this.blockState.key?.root;
4896
4887
  const model = await Models.getMerged(modelKey);
@@ -9393,32 +9384,121 @@ var AnvilParser = class {
9393
9384
  };
9394
9385
 
9395
9386
  // src/world/Chunk.ts
9387
+ import { Vector3 as Vector313 } from "three";
9388
+
9389
+ // src/world/MineRenderWorld.ts
9396
9390
  import { Vector3 as Vector312 } from "three";
9397
9391
 
9392
+ // src/world/SectionModels.ts
9393
+ import { Matrix4 as Matrix46, Vector3 as Vector311 } from "three";
9394
+ var SectionModel = class extends ModelObject {
9395
+ applyTextures() {
9396
+ }
9397
+ };
9398
+ function cached(map, key, load) {
9399
+ let value = map.get(key);
9400
+ if (!value) {
9401
+ value = load().catch((error) => {
9402
+ map.delete(key);
9403
+ throw error;
9404
+ });
9405
+ map.set(key, value);
9406
+ }
9407
+ return value;
9408
+ }
9409
+ var SectionModels = class {
9410
+ constructor(maxAtlasSize = 2048) {
9411
+ this.maxAtlasSize = maxAtlasSize;
9412
+ this.states = /* @__PURE__ */ new WeakMap();
9413
+ this.models = /* @__PURE__ */ new WeakMap();
9414
+ this.templates = /* @__PURE__ */ new Set();
9415
+ if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("maxAtlasSize must be a positive integer");
9416
+ }
9417
+ async get(blockState, properties = {}) {
9418
+ if (!blockState.variants || blockState.multipart) return void 0;
9419
+ let states = this.states.get(blockState);
9420
+ if (!states) this.states.set(blockState, states = /* @__PURE__ */ new Map());
9421
+ const key = JSON.stringify(Object.entries(properties).sort(([a], [b]) => a.localeCompare(b)));
9422
+ const prepared = await cached(states, key, () => this.prepare(blockState, properties));
9423
+ if (!prepared) return void 0;
9424
+ return prepared.templates.get(BlockStateResolver.choose(prepared.variants));
9425
+ }
9426
+ async prepare(blockState, properties) {
9427
+ const state = { ...await BlockStateResolver.defaults(blockState), ...properties };
9428
+ const groups = BlockStateResolver.matching(blockState, state);
9429
+ if (groups.length !== 1) return void 0;
9430
+ const variants = groups[0];
9431
+ const choices = Array.isArray(variants) ? variants : [variants];
9432
+ if (!choices.length) return void 0;
9433
+ const templates = /* @__PURE__ */ new Map();
9434
+ for (const variant of choices) {
9435
+ const model = await Models.getMerged(AssetKey.parse("models", variant.model));
9436
+ if (!model) return void 0;
9437
+ const atlas = await UVMapper.getAtlas(model);
9438
+ const rotation = BlockStateResolver.rotation(variant);
9439
+ 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;
9440
+ const tints = await BlockTints.get(blockState.key, state, model);
9441
+ let models = this.models.get(model);
9442
+ if (!models) this.models.set(model, models = /* @__PURE__ */ new Map());
9443
+ const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints]);
9444
+ const template = await cached(models, key, async () => {
9445
+ const object = new SectionModel(model, {
9446
+ instanceMeshes: false,
9447
+ mergeMeshes: false,
9448
+ tints,
9449
+ uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0) ? [rotation.x, rotation.y, rotation.z] : void 0
9450
+ });
9451
+ await object.init();
9452
+ const geometry = object.children[0].geometry;
9453
+ geometry.applyMatrix4(new Matrix46().makeRotationFromEuler(rotation));
9454
+ object.clear();
9455
+ const cullFaces = CUBE_FACES.map((face) => {
9456
+ const local = CUBE_FACES.findIndex((name) => name === atlas.model.elements[0].faces[face]?.cullface);
9457
+ if (local < 0) return 0;
9458
+ const normal = new Vector311(...CUBE_FACE_OFFSETS[local]).applyEuler(rotation);
9459
+ 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);
9460
+ return 1 << world;
9461
+ });
9462
+ const template2 = { geometry, atlas, cullFaces };
9463
+ this.templates.add(template2);
9464
+ return template2;
9465
+ });
9466
+ templates.set(variant, template);
9467
+ }
9468
+ return { variants, templates };
9469
+ }
9470
+ clear() {
9471
+ for (const template of this.templates) template.geometry.dispose();
9472
+ this.templates.clear();
9473
+ this.states = /* @__PURE__ */ new WeakMap();
9474
+ this.models = /* @__PURE__ */ new WeakMap();
9475
+ }
9476
+ };
9477
+
9398
9478
  // src/world/MineRenderWorld.ts
9399
- import { Vector3 as Vector311 } from "three";
9400
9479
  var MineRenderWorld = class {
9401
- constructor(scene) {
9480
+ constructor(scene, options = {}) {
9402
9481
  this._chunks = /* @__PURE__ */ new Map();
9403
9482
  this.pendingCulling = /* @__PURE__ */ new Map();
9404
9483
  this.scene = scene;
9484
+ if (options.sectionMeshing) this.sectionModels = new SectionModels(options.maxAtlasSize);
9405
9485
  }
9406
9486
  getBlockAt(posOrX, y, z) {
9407
9487
  if (typeof posOrX == "number") {
9408
- return this.getBlockAt(new Vector311(posOrX, y, z));
9488
+ return this.getBlockAt(new Vector312(posOrX, y, z));
9409
9489
  }
9410
9490
  if (isTripleArray(posOrX)) {
9411
- return this.getBlockAt(new Vector311(posOrX[0], posOrX[1], posOrX[2]));
9491
+ return this.getBlockAt(new Vector312(posOrX[0], posOrX[1], posOrX[2]));
9412
9492
  }
9413
9493
  this.validatePosBounds(posOrX);
9414
9494
  return this.getChunkAt(posOrX)?.getBlockAt(posOrX);
9415
9495
  }
9416
9496
  async setBlockAt(posOrX, yOrBlock, z, block) {
9417
9497
  if (typeof posOrX == "number") {
9418
- return this.setBlockAt(new Vector311(posOrX, yOrBlock, z), block);
9498
+ return this.setBlockAt(new Vector312(posOrX, yOrBlock, z), block);
9419
9499
  }
9420
9500
  if (isTripleArray(posOrX)) {
9421
- return this.setBlockAt(new Vector311(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
9501
+ return this.setBlockAt(new Vector312(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
9422
9502
  }
9423
9503
  return this.placeBlock(posOrX, yOrBlock);
9424
9504
  }
@@ -9439,7 +9519,7 @@ var MineRenderWorld = class {
9439
9519
  const collect = async (positions2) => {
9440
9520
  for (const pos of positions2) changes.set(pos.toArray().join(","), pos);
9441
9521
  };
9442
- const place = (block) => this.placeBlock(new Vector311(...block.position), block, collect);
9522
+ const place = (block) => this.placeBlock(new Vector312(...block.position), block, collect);
9443
9523
  const keys = /* @__PURE__ */ new Map();
9444
9524
  for (const block of multiblock.blocks) {
9445
9525
  if (Chunk.isAir(block)) continue;
@@ -9511,12 +9591,13 @@ var MineRenderWorld = class {
9511
9591
  for (const chunk of chunks) {
9512
9592
  await chunk.dispose();
9513
9593
  }
9594
+ this.sectionModels?.clear();
9514
9595
  }
9515
9596
  updateCulling(positions) {
9516
9597
  for (const pos of positions) {
9517
9598
  this.pendingCulling.set(pos.toArray().join(","), pos.clone());
9518
9599
  for (const offset of CUBE_FACE_OFFSETS) {
9519
- const neighbor = pos.clone().add(new Vector311(...offset));
9600
+ const neighbor = pos.clone().add(new Vector312(...offset));
9520
9601
  this.pendingCulling.set(neighbor.toArray().join(","), neighbor);
9521
9602
  }
9522
9603
  }
@@ -9525,17 +9606,22 @@ var MineRenderWorld = class {
9525
9606
  while (this.pendingCulling.size) {
9526
9607
  const batch = [...this.pendingCulling.values()];
9527
9608
  this.pendingCulling.clear();
9609
+ const changed = /* @__PURE__ */ new Set();
9528
9610
  for (const pos of batch) {
9529
- const block = this.getBlockAt(pos)?.object;
9530
- if (!block) continue;
9611
+ const chunk = this.getChunkAt(pos);
9612
+ if (!chunk) continue;
9613
+ changed.add(chunk);
9614
+ if (!chunk.getBlockAt(pos)) continue;
9531
9615
  let mask = 0;
9532
9616
  for (const [face, offset] of CUBE_FACE_OFFSETS.entries()) {
9533
- if (this.getBlockAt(pos.clone().add(new Vector311(...offset)))?.object.isOccluding) {
9617
+ const neighbor = pos.clone().add(new Vector312(...offset));
9618
+ if (this.getChunkAt(neighbor)?.isOccludingAt(neighbor)) {
9534
9619
  mask |= 1 << face;
9535
9620
  }
9536
9621
  }
9537
- await block.setCullMask(mask);
9622
+ await chunk.setCullMaskAt(pos, mask);
9538
9623
  }
9624
+ for (const chunk of changed) chunk.rebuildSectionMesh();
9539
9625
  }
9540
9626
  } finally {
9541
9627
  this.culling = void 0;
@@ -9551,7 +9637,8 @@ var MineRenderWorld = class {
9551
9637
  Math.floor(pos.x / 16),
9552
9638
  Math.floor(pos.y / 16),
9553
9639
  Math.floor(pos.z / 16),
9554
- (positions) => this.updateCulling(positions)
9640
+ (positions) => this.updateCulling(positions),
9641
+ this.sectionModels
9555
9642
  );
9556
9643
  this._chunks.set(key, chunk);
9557
9644
  }
@@ -9561,14 +9648,14 @@ var MineRenderWorld = class {
9561
9648
  return this._chunks.get(this.worldPosToChunkKey(pos));
9562
9649
  }
9563
9650
  static worldToScenePosition(pos) {
9564
- return new Vector311(
9651
+ return new Vector312(
9565
9652
  pos.x * 16,
9566
9653
  pos.y * 16,
9567
9654
  pos.z * 16
9568
9655
  );
9569
9656
  }
9570
9657
  static sceneToWorldPosition(pos) {
9571
- return new Vector311(
9658
+ return new Vector312(
9572
9659
  pos.x / 16,
9573
9660
  pos.y / 16,
9574
9661
  pos.z / 16
@@ -9587,12 +9674,118 @@ var MineRenderWorld = class {
9587
9674
  }
9588
9675
  };
9589
9676
 
9677
+ // src/world/SectionMesh.ts
9678
+ import { BufferGeometry as BufferGeometry2, Float32BufferAttribute as Float32BufferAttribute4, Group, Mesh as Mesh7 } from "three";
9679
+ var SectionMesh = class _SectionMesh extends Group {
9680
+ constructor() {
9681
+ super(...arguments);
9682
+ this.ownedMeshes = [];
9683
+ }
9684
+ static build(entries, maxAtlasSize = 2048) {
9685
+ if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
9686
+ const visible = entries.filter((entry) => entry.template.cullFaces.some((direction) => !(entry.cullMask & direction)));
9687
+ const atlases = [...new Set(visible.map((entry) => entry.template.atlas))].sort((a, b) => b.image.height - a.image.height);
9688
+ const pages = [];
9689
+ const locations = /* @__PURE__ */ new Map();
9690
+ for (const atlas of atlases) {
9691
+ const { width, height } = atlas.image;
9692
+ if (width > maxAtlasSize || height > maxAtlasSize) throw new RangeError("Model atlas exceeds the section atlas size limit");
9693
+ let selected;
9694
+ for (const page of pages) {
9695
+ const nextRow = page.x + width > maxAtlasSize;
9696
+ if ((nextRow ? page.y + page.rowHeight : page.y) + height > maxAtlasSize) continue;
9697
+ if (nextRow) {
9698
+ page.y += page.rowHeight;
9699
+ page.x = 0;
9700
+ page.rowHeight = 0;
9701
+ }
9702
+ selected = page;
9703
+ break;
9704
+ }
9705
+ if (!selected) {
9706
+ selected = { placements: [], entries: [], x: 0, y: 0, rowHeight: 0, width: 0, height: 0 };
9707
+ pages.push(selected);
9708
+ }
9709
+ const placement = { atlas, x: selected.x, y: selected.y };
9710
+ selected.placements.push(placement);
9711
+ selected.x += width;
9712
+ selected.rowHeight = Math.max(selected.rowHeight, height);
9713
+ selected.width = Math.max(selected.width, selected.x);
9714
+ selected.height = Math.max(selected.height, selected.y + height);
9715
+ locations.set(atlas, { page: selected, placement });
9716
+ }
9717
+ for (const entry of visible) locations.get(entry.template.atlas).page.entries.push(entry);
9718
+ const section = new _SectionMesh();
9719
+ for (const page of pages) {
9720
+ const canvas = createCanvas2(page.width, page.height);
9721
+ const context = canvas.getContext("2d");
9722
+ for (const { atlas, x, y } of page.placements) {
9723
+ context.drawImage(atlas.image.canvas, x, y);
9724
+ }
9725
+ const positions = [], normals = [], uvs = [], colors = [], indices = [];
9726
+ for (const { index, template, cullMask } of page.entries) {
9727
+ const position = template.geometry.getAttribute("position");
9728
+ const normal = template.geometry.getAttribute("normal");
9729
+ const uv = template.geometry.getAttribute("uv");
9730
+ const color = template.geometry.getAttribute("color");
9731
+ const sourceIndices = template.geometry.getIndex();
9732
+ const placement = locations.get(template.atlas).placement;
9733
+ const offsetX = index % 16 * 16;
9734
+ const offsetY = Math.floor(index / 256) * 16;
9735
+ const offsetZ = Math.floor(index / 16) % 16 * 16;
9736
+ for (let face = 0; face < 6; face++) {
9737
+ if (cullMask & template.cullFaces[face]) continue;
9738
+ const start = positions.length / 3;
9739
+ for (let corner = 0; corner < 4; corner++) {
9740
+ const vertex = face * 4 + corner;
9741
+ positions.push(position.getX(vertex) + offsetX, position.getY(vertex) + offsetY, position.getZ(vertex) + offsetZ);
9742
+ normals.push(normal.getX(vertex), normal.getY(vertex), normal.getZ(vertex));
9743
+ uvs.push(
9744
+ (placement.x + uv.getX(vertex) * template.atlas.image.width) / page.width,
9745
+ 1 - (placement.y + (1 - uv.getY(vertex)) * template.atlas.image.height) / page.height
9746
+ );
9747
+ colors.push(color?.getX(vertex) ?? 1, color?.getY(vertex) ?? 1, color?.getZ(vertex) ?? 1);
9748
+ }
9749
+ for (let corner = 0; corner < 6; corner++) indices.push(start + sourceIndices.getX(face * 6 + corner) - face * 4);
9750
+ }
9751
+ }
9752
+ const geometry = new BufferGeometry2();
9753
+ geometry.setAttribute("position", new Float32BufferAttribute4(positions, 3));
9754
+ geometry.setAttribute("normal", new Float32BufferAttribute4(normals, 3));
9755
+ geometry.setAttribute("uv", new Float32BufferAttribute4(uvs, 2));
9756
+ geometry.setAttribute("color", new Float32BufferAttribute4(colors, 3));
9757
+ geometry.setIndex(indices);
9758
+ geometry.computeBoundingBox();
9759
+ geometry.computeBoundingSphere();
9760
+ const material = Materials.createShadedCanvasMaterial(canvas, false);
9761
+ material.vertexColors = true;
9762
+ const texture = material.uniforms?.map?.value ?? material.map;
9763
+ const mesh = new Mesh7(geometry, material);
9764
+ section.add(mesh);
9765
+ section.ownedMeshes.push({ mesh, texture });
9766
+ }
9767
+ return section;
9768
+ }
9769
+ dispose() {
9770
+ for (const { mesh, texture } of this.ownedMeshes.splice(0)) {
9771
+ mesh.removeFromParent();
9772
+ mesh.geometry.dispose();
9773
+ mesh.material.dispose();
9774
+ texture?.dispose();
9775
+ }
9776
+ this.removeFromParent();
9777
+ }
9778
+ };
9779
+
9590
9780
  // src/world/Chunk.ts
9591
9781
  var Chunk = class _Chunk {
9592
- constructor(scene, x, y, z, onBlocksChanged) {
9782
+ constructor(scene, x, y, z, onBlocksChanged, sectionModels) {
9593
9783
  this.onBlocksChanged = onBlocksChanged;
9784
+ this.sectionModels = sectionModels;
9594
9785
  this.data = new ChunkData();
9595
9786
  this.renderedBlocks = /* @__PURE__ */ new Map();
9787
+ this.sectionBlocks = /* @__PURE__ */ new Map();
9788
+ this.meshDirty = false;
9596
9789
  this.scene = scene;
9597
9790
  this.x = x;
9598
9791
  this.y = y;
@@ -9600,10 +9793,10 @@ var Chunk = class _Chunk {
9600
9793
  }
9601
9794
  getBlockAt(posOrX, y, z) {
9602
9795
  if (typeof posOrX == "number") {
9603
- return this.getBlockAt(new Vector312(posOrX, y, z));
9796
+ return this.getBlockAt(new Vector313(posOrX, y, z));
9604
9797
  }
9605
9798
  if (isTripleArray(posOrX)) {
9606
- return this.getBlockAt(new Vector312(posOrX[0], posOrX[1], posOrX[2]));
9799
+ return this.getBlockAt(new Vector313(posOrX[0], posOrX[1], posOrX[2]));
9607
9800
  }
9608
9801
  const pos = this.worldPosToChunkPos(posOrX);
9609
9802
  const index = _Chunk.chunkPosToBlockIndex(pos);
@@ -9611,10 +9804,10 @@ var Chunk = class _Chunk {
9611
9804
  }
9612
9805
  async setBlockAt(posOrX, yOrBlock, z, block) {
9613
9806
  if (typeof posOrX == "number") {
9614
- return this.setBlockAt(new Vector312(posOrX, yOrBlock, z), block);
9807
+ return this.setBlockAt(new Vector313(posOrX, yOrBlock, z), block);
9615
9808
  }
9616
9809
  if (isTripleArray(posOrX)) {
9617
- return this.setBlockAt(new Vector312(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
9810
+ return this.setBlockAt(new Vector313(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
9618
9811
  }
9619
9812
  const worldPos = posOrX;
9620
9813
  const pos = this.worldPosToChunkPos(worldPos);
@@ -9631,7 +9824,8 @@ var Chunk = class _Chunk {
9631
9824
  const index = _Chunk.chunkPosToBlockIndex(pos);
9632
9825
  this.data.set(index, block);
9633
9826
  const readBlock = this.data.snapshot(index);
9634
- this.renderedBlocks.get(index)?.object.removeFromScene();
9827
+ this.renderedBlocks.get(index)?.object?.removeFromScene();
9828
+ if (this.sectionBlocks.delete(index)) this.meshDirty = true;
9635
9829
  this.renderedBlocks.delete(index);
9636
9830
  let object;
9637
9831
  try {
@@ -9642,13 +9836,19 @@ var Chunk = class _Chunk {
9642
9836
  this.data.set(index, void 0);
9643
9837
  return void 0;
9644
9838
  }
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));
9839
+ const template = await this.sectionModels?.get(blockState, stored.properties);
9840
+ if (template) {
9841
+ this.sectionBlocks.set(index, { index, template, cullMask: 0 });
9842
+ this.meshDirty = true;
9843
+ } else {
9844
+ object = await this.scene.addBlock(blockState, {
9845
+ mergeMeshes: true,
9846
+ instanceMeshes: true,
9847
+ maxInstanceCount: 2e3,
9848
+ initialState: stored.properties
9849
+ });
9850
+ object.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
9851
+ }
9652
9852
  const info = { get block() {
9653
9853
  return readBlock();
9654
9854
  }, object };
@@ -9662,11 +9862,43 @@ var Chunk = class _Chunk {
9662
9862
  await onBlocksChanged?.([worldPos]);
9663
9863
  }
9664
9864
  }
9865
+ isOccludingAt(pos) {
9866
+ const index = _Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos));
9867
+ return this.sectionBlocks.has(index) || (this.renderedBlocks.get(index)?.object?.isOccluding ?? false);
9868
+ }
9869
+ async setCullMaskAt(pos, mask) {
9870
+ const index = _Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos));
9871
+ const entry = this.sectionBlocks.get(index);
9872
+ if (entry) {
9873
+ if (entry.cullMask !== mask) {
9874
+ entry.cullMask = mask;
9875
+ this.meshDirty = true;
9876
+ }
9877
+ } else {
9878
+ await this.renderedBlocks.get(index)?.object?.setCullMask(mask);
9879
+ }
9880
+ }
9881
+ rebuildSectionMesh() {
9882
+ if (!this.meshDirty) return;
9883
+ const next = this.sectionBlocks.size ? SectionMesh.build([...this.sectionBlocks.values()], this.sectionModels.maxAtlasSize) : void 0;
9884
+ this.sectionMesh?.dispose();
9885
+ this.sectionMesh = next;
9886
+ if (next) {
9887
+ next.position.set(this.x * 256, this.y * 256, this.z * 256);
9888
+ this.scene.add(next);
9889
+ }
9890
+ this.scene.dirty = true;
9891
+ this.meshDirty = false;
9892
+ }
9665
9893
  async clear(onBlocksChanged = this.onBlocksChanged) {
9666
9894
  const positions = [...this.renderedBlocks.keys()].map((index) => this.chunkPosToWorldPos(
9667
- new Vector312(index % 16, Math.floor(index / 256), Math.floor(index / 16) % 16)
9895
+ new Vector313(index % 16, Math.floor(index / 256), Math.floor(index / 16) % 16)
9668
9896
  ));
9669
- for (const info of this.renderedBlocks.values()) info.object.removeFromScene();
9897
+ for (const info of this.renderedBlocks.values()) info.object?.removeFromScene();
9898
+ this.sectionBlocks.clear();
9899
+ this.sectionMesh?.dispose();
9900
+ this.sectionMesh = void 0;
9901
+ this.meshDirty = false;
9670
9902
  this.renderedBlocks.clear();
9671
9903
  this.data.clear();
9672
9904
  await onBlocksChanged?.(positions);
@@ -9684,14 +9916,14 @@ var Chunk = class _Chunk {
9684
9916
  return pos.y * 16 * 16 + pos.z * 16 + pos.x;
9685
9917
  }
9686
9918
  worldPosToChunkPos(pos) {
9687
- return new Vector312(
9919
+ return new Vector313(
9688
9920
  pos.x - this.x * 16,
9689
9921
  pos.y - this.y * 16,
9690
9922
  pos.z - this.z * 16
9691
9923
  );
9692
9924
  }
9693
9925
  chunkPosToWorldPos(pos) {
9694
- return new Vector312(
9926
+ return new Vector313(
9695
9927
  this.x * 16 + pos.x,
9696
9928
  this.y * 16 + pos.y,
9697
9929
  this.z * 16 + pos.z
@@ -9714,6 +9946,7 @@ export {
9714
9946
  BasicAssetKey,
9715
9947
  BatchedExecutor,
9716
9948
  BlockObject,
9949
+ BlockStateResolver,
9717
9950
  BlockStates,
9718
9951
  BlockTints,
9719
9952
  BrowserArchiveProxy,
@@ -9772,6 +10005,8 @@ export {
9772
10005
  SceneStats,
9773
10006
  SceneStatsDisplay,
9774
10007
  SchematicParser,
10008
+ SectionMesh,
10009
+ SectionModels,
9775
10010
  SkinImage,
9776
10011
  SkinObject,
9777
10012
  SkinPart,