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.
@@ -1478,6 +1478,11 @@ var init_DisplayTransforms = __esm({
1478
1478
  }
1479
1479
  /** Build a display pose for geometry already centered at the model's [8, 8, 8] pivot. */
1480
1480
  static getMatrix(display, position) {
1481
+ const transform = this.getTransform(display, position);
1482
+ return new Matrix4().compose(transform.position, transform.quaternion, transform.scale);
1483
+ }
1484
+ /** Position, rotation, and scale of a display pose for geometry already centered at the model's [8, 8, 8] pivot. */
1485
+ static getTransform(display, position) {
1481
1486
  const transform = this.withHandFallbacks(display)?.[position] ?? {};
1482
1487
  const left = position === "firstperson_lefthand" /* FIRSTPERSON_LEFTHAND */ || position === "thirdperson_lefthand" /* THIRDPERSON_LEFTHAND */;
1483
1488
  const sign = left ? -1 : 1;
@@ -1486,7 +1491,7 @@ var init_DisplayTransforms = __esm({
1486
1491
  const translation = new Vector32(...transform.translation ?? [0, 0, 0]).clampScalar(-80, 80);
1487
1492
  translation.x *= sign;
1488
1493
  const scale = new Vector32(...transform.scale ?? [1, 1, 1]).clampScalar(-4, 4);
1489
- return new Matrix4().compose(translation, rotation, scale);
1494
+ return { position: translation, quaternion: rotation, scale };
1490
1495
  }
1491
1496
  /** Transforms geometry in place and reverses triangle winding when the matrix reflects it. */
1492
1497
  static apply(geometry, matrix) {
@@ -1671,6 +1676,7 @@ ${key}` : key, async () => {
1671
1676
  if (!object(data)) return false;
1672
1677
  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)));
1673
1678
  if (dataset === "legacyBlocks") return object(data.blocks) && Object.values(data.blocks).every((value) => typeof value === "string");
1679
+ 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")));
1674
1680
  return Object.values(data).every((value) => {
1675
1681
  if (!object(value)) return false;
1676
1682
  switch (dataset) {
@@ -7014,16 +7020,21 @@ var init_ModelObject = __esm({
7014
7020
  const special = this.originalModel.special;
7015
7021
  if (special) {
7016
7022
  const parts2 = await SpecialItems.getParts(special, this.originalModel.key?.root, this.originalModel.components);
7017
- const transform = this.options.displayPosition ? DisplayTransforms.getMatrix(this.originalModel.display, this.options.displayPosition) : new Matrix48();
7018
- transform.multiply(new Matrix48().makeTranslation(-8, -8, -8));
7023
+ const display = new SceneObject();
7024
+ if (this.options.displayPosition) {
7025
+ const transform = DisplayTransforms.getTransform(this.originalModel.display, this.options.displayPosition);
7026
+ display.position.copy(transform.position);
7027
+ display.quaternion.copy(transform.quaternion);
7028
+ display.scale.copy(transform.scale);
7029
+ }
7030
+ this.add(display);
7031
+ const offset = new Matrix48().makeTranslation(-8, -8, -8);
7019
7032
  try {
7020
7033
  const targets = [];
7021
7034
  for (const part of parts2) {
7022
7035
  const object4 = new EntityObject(part.model, { flip: false, wireframe: this.options.wireframe, tints: part.tints, faces: part.faces });
7023
- object4.matrix.copy(transform).multiply(part.transform);
7024
- object4.matrixWorldNeedsUpdate = true;
7025
- object4.matrixAutoUpdate = false;
7026
- this.add(object4);
7036
+ object4.matrix.multiplyMatrices(offset, part.transform).decompose(object4.position, object4.quaternion, object4.scale);
7037
+ display.add(object4);
7027
7038
  await object4.init(part.material);
7028
7039
  object4.iterateAllMeshes((mesh) => {
7029
7040
  const source = mesh.material;
@@ -17626,6 +17637,38 @@ function sampleSectionLight(light, index) {
17626
17637
  };
17627
17638
  }
17628
17639
 
17640
+ // src/world/_culling/BlockCulling.ts
17641
+ init_Assets();
17642
+ init_MineRenderData();
17643
+ init_CubeFace();
17644
+ async function loadBlockCulling(root) {
17645
+ const { manifest } = await MineRenderData.resolve(root);
17646
+ return manifest.datasets.blockCulling ? MineRenderData.get("blockCulling", root) : void 0;
17647
+ }
17648
+ function blockCullingRule(data, type) {
17649
+ if (!type.includes(":")) type = `${DEFAULT_NAMESPACE}:${type}`;
17650
+ return Object.prototype.hasOwnProperty.call(data.blocks, type) ? data.profiles[data.blocks[type]] : void 0;
17651
+ }
17652
+ function skipsBlockFace(data, rule, block, neighbor, face) {
17653
+ const type = block.type.includes(":") ? block.type : `${DEFAULT_NAMESPACE}:${block.type}`;
17654
+ const other = neighbor.type.includes(":") ? neighbor.type : `${DEFAULT_NAMESPACE}:${neighbor.type}`;
17655
+ const same = type === other;
17656
+ const vertical = CUBE_FACES[face] === "up" /* UP */ || CUBE_FACES[face] === "down" /* DOWN */;
17657
+ switch (rule.type) {
17658
+ case "same_block":
17659
+ return same;
17660
+ case "same_block_vertical":
17661
+ return same && vertical;
17662
+ case "connected": {
17663
+ if (vertical) return same;
17664
+ const group = rule.group ? data.groups?.[rule.group] : void 0;
17665
+ return (same || !!group?.includes(type) && group.includes(other)) && String(block.properties?.[CUBE_FACES[face]]) === "true" && String(neighbor.properties?.[CUBE_FACES[face ^ 1]]) === "true";
17666
+ }
17667
+ default:
17668
+ return false;
17669
+ }
17670
+ }
17671
+
17629
17672
  // src/world/MineRenderWorld.ts
17630
17673
  var MineRenderWorld = class {
17631
17674
  constructor(scene, options = {}) {
@@ -18014,15 +18057,23 @@ var MineRenderWorld = class {
18014
18057
  return this.culling ?? (this.culling = Promise.resolve().then(async () => {
18015
18058
  let sliceStart = performance.now();
18016
18059
  try {
18060
+ const culling = await loadBlockCulling();
18017
18061
  while (this.pendingCulling.size) {
18018
18062
  const batch = [...this.pendingCulling];
18019
18063
  this.pendingCulling.clear();
18020
18064
  for (const [chunk, indices] of batch) {
18021
18065
  const neighbors = CUBE_FACE_OFFSETS.map(([x, y, z]) => this._chunks.get(`${chunk.x + x}_${chunk.y + y}_${chunk.z + z}`));
18022
- const masks = chunk.updateCullMasks(indices === "all" ? void 0 : indices, (x, y, z) => {
18066
+ const neighborAt = (x, y, z) => {
18023
18067
  const lx = x - chunk.x * 16, ly = y - chunk.y * 16, lz = z - chunk.z * 16;
18024
18068
  const section = neighbors[lx >= 16 ? 0 : lx < 0 ? 1 : ly >= 16 ? 2 : ly < 0 ? 3 : lz >= 16 ? 4 : 5];
18025
- return section?.isOccludingIndex((ly & 15) * 256 + (lz & 15) * 16 + (lx & 15)) ?? false;
18069
+ return [section, (ly & 15) * 256 + (lz & 15) * 16 + (lx & 15)];
18070
+ };
18071
+ const masks = chunk.updateCullMasks(indices === "all" ? void 0 : indices, (x, y, z) => {
18072
+ const [section, i] = neighborAt(x, y, z);
18073
+ return section?.isOccludingIndex(i) ?? false;
18074
+ }, culling, (x, y, z) => {
18075
+ const [section, i] = neighborAt(x, y, z);
18076
+ return section?.visibleBlockIndex(i);
18026
18077
  });
18027
18078
  const fluidIndices = indices === "all" ? function* () {
18028
18079
  for (let index = 0; index < 4096; index++) yield index;
@@ -19137,6 +19188,10 @@ var Chunk = class _Chunk {
19137
19188
  isBlockVisibleIndex(index) {
19138
19189
  return this.renderedBlocks.has(index) && !this.hiddenBlocks.has(index);
19139
19190
  }
19191
+ /** Stored state of a rendered, visible cell, or `undefined`. */
19192
+ visibleBlockIndex(index) {
19193
+ return this.isBlockVisibleIndex(index) ? this.data.stateAt(this.data.idAt(index)) : void 0;
19194
+ }
19140
19195
  /** Fluid kind bits and level of a cell, or 0. Hidden cells report 0. */
19141
19196
  fluidByteIndex(index) {
19142
19197
  return this.hiddenBlocks.has(index) ? 0 : this.fluidCells[index];
@@ -19155,18 +19210,30 @@ var Chunk = class _Chunk {
19155
19210
  async setCullMaskAt(pos, mask) {
19156
19211
  await this.setCullMaskIndex(_Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos)), mask);
19157
19212
  }
19158
- /** Recomputes section masks and returns changed per-block masks. Omit indices to visit every cell. */
19159
- updateCullMasks(indices, occludes) {
19213
+ /**
19214
+ * Recomputes section masks and returns changed per-block masks. Omit indices to visit every cell.
19215
+ * `culling` supplies neighbor face rules; `blockAt` supplies visible states outside this chunk.
19216
+ */
19217
+ updateCullMasks(indices, occludes, culling, blockAt) {
19160
19218
  const changed = [];
19219
+ const rules = [];
19161
19220
  for (const index of indices ?? this.fluidCells.keys()) {
19162
19221
  if (!this.isBlockVisibleIndex(index)) continue;
19163
19222
  const x = index % 16, y = Math.floor(index / 256), z = Math.floor(index / 16) % 16;
19223
+ const id = this.data.idAt(index);
19224
+ let rule = rules[id];
19225
+ if (culling && rule === void 0) rules[id] = rule = blockCullingRule(culling, this.data.stateAt(id).type) ?? null;
19226
+ const block = rule ? this.data.stateAt(id) : void 0;
19164
19227
  let mask = 0;
19165
19228
  for (let face = 0; face < CUBE_FACE_OFFSETS.length; face++) {
19166
19229
  const [dx, dy, dz] = CUBE_FACE_OFFSETS[face];
19167
19230
  const nx = x + dx, ny = y + dy, nz = z + dz;
19168
19231
  const inner = nx >= 0 && nx < 16 && ny >= 0 && ny < 16 && nz >= 0 && nz < 16;
19169
19232
  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;
19233
+ else if (rule) {
19234
+ const neighbor = inner ? this.visibleBlockIndex(ny * 256 + nz * 16 + nx) : blockAt?.(this.x * 16 + nx, this.y * 16 + ny, this.z * 16 + nz);
19235
+ if (neighbor && skipsBlockFace(culling, rule, block, neighbor, face)) mask |= 1 << face;
19236
+ }
19170
19237
  }
19171
19238
  if (this.fluidCells[index]) this.meshDirty = true;
19172
19239
  const entries = this.sectionBlocks.get(index);