minerender 2.1.0-alpha.126 → 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.
@@ -1310,6 +1310,15 @@ interface MineRenderDatasets {
1310
1310
  untinted?: number[];
1311
1311
  biome?: BiomeColorKind;
1312
1312
  }>;
1313
+ blockCulling: {
1314
+ profiles: {
1315
+ type: string;
1316
+ group?: string;
1317
+ kind?: string;
1318
+ }[];
1319
+ blocks: Record<string, number>;
1320
+ groups?: Record<string, string[]>;
1321
+ };
1313
1322
  fluids: Record<string, {
1314
1323
  kind: "water" | "lava";
1315
1324
  renderModel: boolean;
@@ -3895,6 +3904,8 @@ interface WorldBlockIntersection {
3895
3904
  distance: number;
3896
3905
  }
3897
3906
 
3907
+ type BlockCullingData = MineRenderDatasets["blockCulling"];
3908
+
3898
3909
  /**
3899
3910
  * A 16×16×16 block section and its render objects.
3900
3911
  * Constructor coordinates identify the section. Block accessors use world block coordinates
@@ -3971,6 +3982,8 @@ declare class Chunk<SectionMeshing extends boolean = false> {
3971
3982
  isBlockVisibleAt(pos: Vector3): boolean;
3972
3983
  /** Reports whether a cell has a visible render object or section entry. */
3973
3984
  isBlockVisibleIndex(index: number): boolean;
3985
+ /** Stored state of a rendered, visible cell, or `undefined`. */
3986
+ visibleBlockIndex(index: number): Maybe<Pick<Block, "type" | "properties">>;
3974
3987
  /** Fluid kind bits and level of a cell, or 0. Hidden cells report 0. */
3975
3988
  fluidByteIndex(index: number): number;
3976
3989
  /** Whether the cell holds a fluid, merged or per-block. */
@@ -3979,8 +3992,11 @@ declare class Chunk<SectionMeshing extends boolean = false> {
3979
3992
  /** Reports whether a visible cell hides neighboring cube faces. */
3980
3993
  isOccludingIndex(index: number): boolean;
3981
3994
  setCullMaskAt(pos: Vector3, mask: number): Promise<void>;
3982
- /** Recomputes section masks and returns changed per-block masks. Omit indices to visit every cell. */
3983
- updateCullMasks(indices: Iterable<number> | undefined, occludes: (x: number, y: number, z: number) => boolean): [number, number][];
3995
+ /**
3996
+ * Recomputes section masks and returns changed per-block masks. Omit indices to visit every cell.
3997
+ * `culling` supplies neighbor face rules; `blockAt` supplies visible states outside this chunk.
3998
+ */
3999
+ updateCullMasks(indices: Iterable<number> | undefined, occludes: (x: number, y: number, z: number) => boolean, culling?: BlockCullingData, blockAt?: (x: number, y: number, z: number) => Maybe<Pick<Block, "type" | "properties">>): [number, number][];
3984
4000
  /** Updates hidden faces for a cell's section entry or individual render object. */
3985
4001
  setCullMaskIndex(index: number, mask: number): Promise<void>;
3986
4002
  /** Rebuilds visible section geometry and discards results superseded by another rebuild or clear. */
@@ -1310,6 +1310,15 @@ interface MineRenderDatasets {
1310
1310
  untinted?: number[];
1311
1311
  biome?: BiomeColorKind;
1312
1312
  }>;
1313
+ blockCulling: {
1314
+ profiles: {
1315
+ type: string;
1316
+ group?: string;
1317
+ kind?: string;
1318
+ }[];
1319
+ blocks: Record<string, number>;
1320
+ groups?: Record<string, string[]>;
1321
+ };
1313
1322
  fluids: Record<string, {
1314
1323
  kind: "water" | "lava";
1315
1324
  renderModel: boolean;
@@ -3895,6 +3904,8 @@ interface WorldBlockIntersection {
3895
3904
  distance: number;
3896
3905
  }
3897
3906
 
3907
+ type BlockCullingData = MineRenderDatasets["blockCulling"];
3908
+
3898
3909
  /**
3899
3910
  * A 16×16×16 block section and its render objects.
3900
3911
  * Constructor coordinates identify the section. Block accessors use world block coordinates
@@ -3971,6 +3982,8 @@ declare class Chunk<SectionMeshing extends boolean = false> {
3971
3982
  isBlockVisibleAt(pos: Vector3): boolean;
3972
3983
  /** Reports whether a cell has a visible render object or section entry. */
3973
3984
  isBlockVisibleIndex(index: number): boolean;
3985
+ /** Stored state of a rendered, visible cell, or `undefined`. */
3986
+ visibleBlockIndex(index: number): Maybe<Pick<Block, "type" | "properties">>;
3974
3987
  /** Fluid kind bits and level of a cell, or 0. Hidden cells report 0. */
3975
3988
  fluidByteIndex(index: number): number;
3976
3989
  /** Whether the cell holds a fluid, merged or per-block. */
@@ -3979,8 +3992,11 @@ declare class Chunk<SectionMeshing extends boolean = false> {
3979
3992
  /** Reports whether a visible cell hides neighboring cube faces. */
3980
3993
  isOccludingIndex(index: number): boolean;
3981
3994
  setCullMaskAt(pos: Vector3, mask: number): Promise<void>;
3982
- /** Recomputes section masks and returns changed per-block masks. Omit indices to visit every cell. */
3983
- updateCullMasks(indices: Iterable<number> | undefined, occludes: (x: number, y: number, z: number) => boolean): [number, number][];
3995
+ /**
3996
+ * Recomputes section masks and returns changed per-block masks. Omit indices to visit every cell.
3997
+ * `culling` supplies neighbor face rules; `blockAt` supplies visible states outside this chunk.
3998
+ */
3999
+ updateCullMasks(indices: Iterable<number> | undefined, occludes: (x: number, y: number, z: number) => boolean, culling?: BlockCullingData, blockAt?: (x: number, y: number, z: number) => Maybe<Pick<Block, "type" | "properties">>): [number, number][];
3984
4000
  /** Updates hidden faces for a cell's section entry or individual render object. */
3985
4001
  setCullMaskIndex(index: number, mask: number): Promise<void>;
3986
4002
  /** Rebuilds visible section geometry and discards results superseded by another rebuild or clear. */
@@ -1677,6 +1677,7 @@ ${key}` : key, async () => {
1677
1677
  if (!object(data)) return false;
1678
1678
  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)));
1679
1679
  if (dataset === "legacyBlocks") return object(data.blocks) && Object.values(data.blocks).every((value) => typeof value === "string");
1680
+ 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")));
1680
1681
  return Object.values(data).every((value) => {
1681
1682
  if (!object(value)) return false;
1682
1683
  switch (dataset) {
@@ -17799,6 +17800,38 @@ function sampleSectionLight(light, index) {
17799
17800
  };
17800
17801
  }
17801
17802
 
17803
+ // src/world/_culling/BlockCulling.ts
17804
+ init_Assets();
17805
+ init_MineRenderData();
17806
+ init_CubeFace();
17807
+ async function loadBlockCulling(root) {
17808
+ const { manifest } = await MineRenderData.resolve(root);
17809
+ return manifest.datasets.blockCulling ? MineRenderData.get("blockCulling", root) : void 0;
17810
+ }
17811
+ function blockCullingRule(data, type) {
17812
+ if (!type.includes(":")) type = `${DEFAULT_NAMESPACE}:${type}`;
17813
+ return Object.prototype.hasOwnProperty.call(data.blocks, type) ? data.profiles[data.blocks[type]] : void 0;
17814
+ }
17815
+ function skipsBlockFace(data, rule, block, neighbor, face) {
17816
+ const type = block.type.includes(":") ? block.type : `${DEFAULT_NAMESPACE}:${block.type}`;
17817
+ const other = neighbor.type.includes(":") ? neighbor.type : `${DEFAULT_NAMESPACE}:${neighbor.type}`;
17818
+ const same = type === other;
17819
+ const vertical = CUBE_FACES[face] === "up" /* UP */ || CUBE_FACES[face] === "down" /* DOWN */;
17820
+ switch (rule.type) {
17821
+ case "same_block":
17822
+ return same;
17823
+ case "same_block_vertical":
17824
+ return same && vertical;
17825
+ case "connected": {
17826
+ if (vertical) return same;
17827
+ const group = rule.group ? data.groups?.[rule.group] : void 0;
17828
+ return (same || !!group?.includes(type) && group.includes(other)) && String(block.properties?.[CUBE_FACES[face]]) === "true" && String(neighbor.properties?.[CUBE_FACES[face ^ 1]]) === "true";
17829
+ }
17830
+ default:
17831
+ return false;
17832
+ }
17833
+ }
17834
+
17802
17835
  // src/world/MineRenderWorld.ts
17803
17836
  var MineRenderWorld = class {
17804
17837
  constructor(scene, options = {}) {
@@ -18187,15 +18220,23 @@ var MineRenderWorld = class {
18187
18220
  return this.culling ?? (this.culling = Promise.resolve().then(async () => {
18188
18221
  let sliceStart = performance.now();
18189
18222
  try {
18223
+ const culling = await loadBlockCulling();
18190
18224
  while (this.pendingCulling.size) {
18191
18225
  const batch = [...this.pendingCulling];
18192
18226
  this.pendingCulling.clear();
18193
18227
  for (const [chunk, indices] of batch) {
18194
18228
  const neighbors = CUBE_FACE_OFFSETS.map(([x, y, z]) => this._chunks.get(`${chunk.x + x}_${chunk.y + y}_${chunk.z + z}`));
18195
- const masks = chunk.updateCullMasks(indices === "all" ? void 0 : indices, (x, y, z) => {
18229
+ const neighborAt = (x, y, z) => {
18196
18230
  const lx = x - chunk.x * 16, ly = y - chunk.y * 16, lz = z - chunk.z * 16;
18197
18231
  const section = neighbors[lx >= 16 ? 0 : lx < 0 ? 1 : ly >= 16 ? 2 : ly < 0 ? 3 : lz >= 16 ? 4 : 5];
18198
- return section?.isOccludingIndex((ly & 15) * 256 + (lz & 15) * 16 + (lx & 15)) ?? false;
18232
+ return [section, (ly & 15) * 256 + (lz & 15) * 16 + (lx & 15)];
18233
+ };
18234
+ const masks = chunk.updateCullMasks(indices === "all" ? void 0 : indices, (x, y, z) => {
18235
+ const [section, i] = neighborAt(x, y, z);
18236
+ return section?.isOccludingIndex(i) ?? false;
18237
+ }, culling, (x, y, z) => {
18238
+ const [section, i] = neighborAt(x, y, z);
18239
+ return section?.visibleBlockIndex(i);
18199
18240
  });
18200
18241
  const fluidIndices = indices === "all" ? function* () {
18201
18242
  for (let index = 0; index < 4096; index++) yield index;
@@ -19310,6 +19351,10 @@ var Chunk = class _Chunk {
19310
19351
  isBlockVisibleIndex(index) {
19311
19352
  return this.renderedBlocks.has(index) && !this.hiddenBlocks.has(index);
19312
19353
  }
19354
+ /** Stored state of a rendered, visible cell, or `undefined`. */
19355
+ visibleBlockIndex(index) {
19356
+ return this.isBlockVisibleIndex(index) ? this.data.stateAt(this.data.idAt(index)) : void 0;
19357
+ }
19313
19358
  /** Fluid kind bits and level of a cell, or 0. Hidden cells report 0. */
19314
19359
  fluidByteIndex(index) {
19315
19360
  return this.hiddenBlocks.has(index) ? 0 : this.fluidCells[index];
@@ -19328,18 +19373,30 @@ var Chunk = class _Chunk {
19328
19373
  async setCullMaskAt(pos, mask) {
19329
19374
  await this.setCullMaskIndex(_Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos)), mask);
19330
19375
  }
19331
- /** Recomputes section masks and returns changed per-block masks. Omit indices to visit every cell. */
19332
- updateCullMasks(indices, occludes) {
19376
+ /**
19377
+ * Recomputes section masks and returns changed per-block masks. Omit indices to visit every cell.
19378
+ * `culling` supplies neighbor face rules; `blockAt` supplies visible states outside this chunk.
19379
+ */
19380
+ updateCullMasks(indices, occludes, culling, blockAt) {
19333
19381
  const changed = [];
19382
+ const rules = [];
19334
19383
  for (const index of indices ?? this.fluidCells.keys()) {
19335
19384
  if (!this.isBlockVisibleIndex(index)) continue;
19336
19385
  const x = index % 16, y = Math.floor(index / 256), z = Math.floor(index / 16) % 16;
19386
+ const id = this.data.idAt(index);
19387
+ let rule = rules[id];
19388
+ if (culling && rule === void 0) rules[id] = rule = blockCullingRule(culling, this.data.stateAt(id).type) ?? null;
19389
+ const block = rule ? this.data.stateAt(id) : void 0;
19337
19390
  let mask = 0;
19338
19391
  for (let face = 0; face < CUBE_FACE_OFFSETS.length; face++) {
19339
19392
  const [dx, dy, dz] = CUBE_FACE_OFFSETS[face];
19340
19393
  const nx = x + dx, ny = y + dy, nz = z + dz;
19341
19394
  const inner = nx >= 0 && nx < 16 && ny >= 0 && ny < 16 && nz >= 0 && nz < 16;
19342
19395
  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;
19396
+ else if (rule) {
19397
+ const neighbor = inner ? this.visibleBlockIndex(ny * 256 + nz * 16 + nx) : blockAt?.(this.x * 16 + nx, this.y * 16 + ny, this.z * 16 + nz);
19398
+ if (neighbor && skipsBlockFace(culling, rule, block, neighbor, face)) mask |= 1 << face;
19399
+ }
19343
19400
  }
19344
19401
  if (this.fluidCells[index]) this.meshDirty = true;
19345
19402
  const entries = this.sectionBlocks.get(index);