minerender 2.1.0-alpha.125 → 2.1.0-alpha.127

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.
@@ -1534,6 +1534,11 @@ var init_DisplayTransforms = __esm({
1534
1534
  }
1535
1535
  /** Build a display pose for geometry already centered at the model's [8, 8, 8] pivot. */
1536
1536
  static getMatrix(display, position) {
1537
+ const transform = this.getTransform(display, position);
1538
+ return new Matrix4().compose(transform.position, transform.quaternion, transform.scale);
1539
+ }
1540
+ /** Position, rotation, and scale of a display pose for geometry already centered at the model's [8, 8, 8] pivot. */
1541
+ static getTransform(display, position) {
1537
1542
  const transform = this.withHandFallbacks(display)?.[position] ?? {};
1538
1543
  const left = position === "firstperson_lefthand" /* FIRSTPERSON_LEFTHAND */ || position === "thirdperson_lefthand" /* THIRDPERSON_LEFTHAND */;
1539
1544
  const sign = left ? -1 : 1;
@@ -1542,7 +1547,7 @@ var init_DisplayTransforms = __esm({
1542
1547
  const translation = new Vector32(...transform.translation ?? [0, 0, 0]).clampScalar(-80, 80);
1543
1548
  translation.x *= sign;
1544
1549
  const scale = new Vector32(...transform.scale ?? [1, 1, 1]).clampScalar(-4, 4);
1545
- return new Matrix4().compose(translation, rotation, scale);
1550
+ return { position: translation, quaternion: rotation, scale };
1546
1551
  }
1547
1552
  /** Transforms geometry in place and reverses triangle winding when the matrix reflects it. */
1548
1553
  static apply(geometry, matrix) {
@@ -1727,6 +1732,7 @@ ${key}` : key, async () => {
1727
1732
  if (!object(data)) return false;
1728
1733
  if (dataset === "potionColors") return object(data.effects) && Object.values(data.effects).every(Number.isInteger) && object(data.potions) && Object.values(data.potions).every((effects) => Array.isArray(effects) && effects.every((effect) => object(effect) && typeof effect.id === "string" && Number.isInteger(effect.amplifier)));
1729
1734
  if (dataset === "legacyBlocks") return object(data.blocks) && Object.values(data.blocks).every((value) => typeof value === "string");
1735
+ if (dataset === "blockCulling") return Array.isArray(data.profiles) && data.profiles.every((profile) => object(profile) && typeof profile.type === "string") && object(data.blocks) && Object.values(data.blocks).every((index) => Number.isInteger(index) && index >= 0 && index < data.profiles.length) && (data.groups === void 0 || object(data.groups) && Object.values(data.groups).every((group) => Array.isArray(group) && group.every((type) => typeof type === "string")));
1730
1736
  return Object.values(data).every((value) => {
1731
1737
  if (!object(value)) return false;
1732
1738
  switch (dataset) {
@@ -7116,16 +7122,21 @@ var init_ModelObject = __esm({
7116
7122
  const special = this.originalModel.special;
7117
7123
  if (special) {
7118
7124
  const parts2 = await SpecialItems.getParts(special, this.originalModel.key?.root, this.originalModel.components);
7119
- const transform = this.options.displayPosition ? DisplayTransforms.getMatrix(this.originalModel.display, this.options.displayPosition) : new Matrix48();
7120
- transform.multiply(new Matrix48().makeTranslation(-8, -8, -8));
7125
+ const display = new SceneObject();
7126
+ if (this.options.displayPosition) {
7127
+ const transform = DisplayTransforms.getTransform(this.originalModel.display, this.options.displayPosition);
7128
+ display.position.copy(transform.position);
7129
+ display.quaternion.copy(transform.quaternion);
7130
+ display.scale.copy(transform.scale);
7131
+ }
7132
+ this.add(display);
7133
+ const offset = new Matrix48().makeTranslation(-8, -8, -8);
7121
7134
  try {
7122
7135
  const targets = [];
7123
7136
  for (const part of parts2) {
7124
7137
  const object4 = new EntityObject(part.model, { flip: false, wireframe: this.options.wireframe, tints: part.tints, faces: part.faces });
7125
- object4.matrix.copy(transform).multiply(part.transform);
7126
- object4.matrixWorldNeedsUpdate = true;
7127
- object4.matrixAutoUpdate = false;
7128
- this.add(object4);
7138
+ object4.matrix.multiplyMatrices(offset, part.transform).decompose(object4.position, object4.quaternion, object4.scale);
7139
+ display.add(object4);
7129
7140
  await object4.init(part.material);
7130
7141
  object4.iterateAllMeshes((mesh) => {
7131
7142
  const source = mesh.material;
@@ -17662,6 +17673,38 @@ function sampleSectionLight(light, index) {
17662
17673
  };
17663
17674
  }
17664
17675
 
17676
+ // src/world/_culling/BlockCulling.ts
17677
+ init_Assets();
17678
+ init_MineRenderData();
17679
+ init_CubeFace();
17680
+ async function loadBlockCulling(root) {
17681
+ const { manifest } = await MineRenderData.resolve(root);
17682
+ return manifest.datasets.blockCulling ? MineRenderData.get("blockCulling", root) : void 0;
17683
+ }
17684
+ function blockCullingRule(data, type) {
17685
+ if (!type.includes(":")) type = `${DEFAULT_NAMESPACE}:${type}`;
17686
+ return Object.prototype.hasOwnProperty.call(data.blocks, type) ? data.profiles[data.blocks[type]] : void 0;
17687
+ }
17688
+ function skipsBlockFace(data, rule, block, neighbor, face) {
17689
+ const type = block.type.includes(":") ? block.type : `${DEFAULT_NAMESPACE}:${block.type}`;
17690
+ const other = neighbor.type.includes(":") ? neighbor.type : `${DEFAULT_NAMESPACE}:${neighbor.type}`;
17691
+ const same = type === other;
17692
+ const vertical = CUBE_FACES[face] === "up" /* UP */ || CUBE_FACES[face] === "down" /* DOWN */;
17693
+ switch (rule.type) {
17694
+ case "same_block":
17695
+ return same;
17696
+ case "same_block_vertical":
17697
+ return same && vertical;
17698
+ case "connected": {
17699
+ if (vertical) return same;
17700
+ const group = rule.group ? data.groups?.[rule.group] : void 0;
17701
+ return (same || !!group?.includes(type) && group.includes(other)) && String(block.properties?.[CUBE_FACES[face]]) === "true" && String(neighbor.properties?.[CUBE_FACES[face ^ 1]]) === "true";
17702
+ }
17703
+ default:
17704
+ return false;
17705
+ }
17706
+ }
17707
+
17665
17708
  // src/world/MineRenderWorld.ts
17666
17709
  var MineRenderWorld = class {
17667
17710
  constructor(scene, options = {}) {
@@ -18050,15 +18093,23 @@ var MineRenderWorld = class {
18050
18093
  return this.culling ??= Promise.resolve().then(async () => {
18051
18094
  let sliceStart = performance.now();
18052
18095
  try {
18096
+ const culling = await loadBlockCulling();
18053
18097
  while (this.pendingCulling.size) {
18054
18098
  const batch = [...this.pendingCulling];
18055
18099
  this.pendingCulling.clear();
18056
18100
  for (const [chunk, indices] of batch) {
18057
18101
  const neighbors = CUBE_FACE_OFFSETS.map(([x, y, z]) => this._chunks.get(`${chunk.x + x}_${chunk.y + y}_${chunk.z + z}`));
18058
- const masks = chunk.updateCullMasks(indices === "all" ? void 0 : indices, (x, y, z) => {
18102
+ const neighborAt = (x, y, z) => {
18059
18103
  const lx = x - chunk.x * 16, ly = y - chunk.y * 16, lz = z - chunk.z * 16;
18060
18104
  const section = neighbors[lx >= 16 ? 0 : lx < 0 ? 1 : ly >= 16 ? 2 : ly < 0 ? 3 : lz >= 16 ? 4 : 5];
18061
- return section?.isOccludingIndex((ly & 15) * 256 + (lz & 15) * 16 + (lx & 15)) ?? false;
18105
+ return [section, (ly & 15) * 256 + (lz & 15) * 16 + (lx & 15)];
18106
+ };
18107
+ const masks = chunk.updateCullMasks(indices === "all" ? void 0 : indices, (x, y, z) => {
18108
+ const [section, i] = neighborAt(x, y, z);
18109
+ return section?.isOccludingIndex(i) ?? false;
18110
+ }, culling, (x, y, z) => {
18111
+ const [section, i] = neighborAt(x, y, z);
18112
+ return section?.visibleBlockIndex(i);
18062
18113
  });
18063
18114
  const fluidIndices = indices === "all" ? function* () {
18064
18115
  for (let index = 0; index < 4096; index++) yield index;
@@ -19174,6 +19225,10 @@ var Chunk = class _Chunk {
19174
19225
  isBlockVisibleIndex(index) {
19175
19226
  return this.renderedBlocks.has(index) && !this.hiddenBlocks.has(index);
19176
19227
  }
19228
+ /** Stored state of a rendered, visible cell, or `undefined`. */
19229
+ visibleBlockIndex(index) {
19230
+ return this.isBlockVisibleIndex(index) ? this.data.stateAt(this.data.idAt(index)) : void 0;
19231
+ }
19177
19232
  /** Fluid kind bits and level of a cell, or 0. Hidden cells report 0. */
19178
19233
  fluidByteIndex(index) {
19179
19234
  return this.hiddenBlocks.has(index) ? 0 : this.fluidCells[index];
@@ -19192,18 +19247,30 @@ var Chunk = class _Chunk {
19192
19247
  async setCullMaskAt(pos, mask) {
19193
19248
  await this.setCullMaskIndex(_Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos)), mask);
19194
19249
  }
19195
- /** Recomputes section masks and returns changed per-block masks. Omit indices to visit every cell. */
19196
- updateCullMasks(indices, occludes) {
19250
+ /**
19251
+ * Recomputes section masks and returns changed per-block masks. Omit indices to visit every cell.
19252
+ * `culling` supplies neighbor face rules; `blockAt` supplies visible states outside this chunk.
19253
+ */
19254
+ updateCullMasks(indices, occludes, culling, blockAt) {
19197
19255
  const changed = [];
19256
+ const rules = [];
19198
19257
  for (const index of indices ?? this.fluidCells.keys()) {
19199
19258
  if (!this.isBlockVisibleIndex(index)) continue;
19200
19259
  const x = index % 16, y = Math.floor(index / 256), z = Math.floor(index / 16) % 16;
19260
+ const id = this.data.idAt(index);
19261
+ let rule = rules[id];
19262
+ if (culling && rule === void 0) rules[id] = rule = blockCullingRule(culling, this.data.stateAt(id).type) ?? null;
19263
+ const block = rule ? this.data.stateAt(id) : void 0;
19201
19264
  let mask = 0;
19202
19265
  for (let face = 0; face < CUBE_FACE_OFFSETS.length; face++) {
19203
19266
  const [dx, dy, dz] = CUBE_FACE_OFFSETS[face];
19204
19267
  const nx = x + dx, ny = y + dy, nz = z + dz;
19205
19268
  const inner = nx >= 0 && nx < 16 && ny >= 0 && ny < 16 && nz >= 0 && nz < 16;
19206
19269
  if (inner ? this.isOccludingIndex(ny * 256 + nz * 16 + nx) : occludes(this.x * 16 + nx, this.y * 16 + ny, this.z * 16 + nz)) mask |= 1 << face;
19270
+ else if (rule) {
19271
+ const neighbor = inner ? this.visibleBlockIndex(ny * 256 + nz * 16 + nx) : blockAt?.(this.x * 16 + nx, this.y * 16 + ny, this.z * 16 + nz);
19272
+ if (neighbor && skipsBlockFace(culling, rule, block, neighbor, face)) mask |= 1 << face;
19273
+ }
19207
19274
  }
19208
19275
  if (this.fluidCells[index]) this.meshDirty = true;
19209
19276
  const entries = this.sectionBlocks.get(index);