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.
@@ -1312,6 +1312,15 @@ interface MineRenderDatasets {
1312
1312
  untinted?: number[];
1313
1313
  biome?: BiomeColorKind;
1314
1314
  }>;
1315
+ blockCulling: {
1316
+ profiles: {
1317
+ type: string;
1318
+ group?: string;
1319
+ kind?: string;
1320
+ }[];
1321
+ blocks: Record<string, number>;
1322
+ groups?: Record<string, string[]>;
1323
+ };
1315
1324
  fluids: Record<string, {
1316
1325
  kind: "water" | "lava";
1317
1326
  renderModel: boolean;
@@ -3897,6 +3906,8 @@ interface WorldBlockIntersection {
3897
3906
  distance: number;
3898
3907
  }
3899
3908
 
3909
+ type BlockCullingData = MineRenderDatasets["blockCulling"];
3910
+
3900
3911
  /**
3901
3912
  * A 16×16×16 block section and its render objects.
3902
3913
  * Constructor coordinates identify the section. Block accessors use world block coordinates
@@ -3973,6 +3984,8 @@ declare class Chunk<SectionMeshing extends boolean = false> {
3973
3984
  isBlockVisibleAt(pos: Vector3): boolean;
3974
3985
  /** Reports whether a cell has a visible render object or section entry. */
3975
3986
  isBlockVisibleIndex(index: number): boolean;
3987
+ /** Stored state of a rendered, visible cell, or `undefined`. */
3988
+ visibleBlockIndex(index: number): Maybe<Pick<Block, "type" | "properties">>;
3976
3989
  /** Fluid kind bits and level of a cell, or 0. Hidden cells report 0. */
3977
3990
  fluidByteIndex(index: number): number;
3978
3991
  /** Whether the cell holds a fluid, merged or per-block. */
@@ -3981,8 +3994,11 @@ declare class Chunk<SectionMeshing extends boolean = false> {
3981
3994
  /** Reports whether a visible cell hides neighboring cube faces. */
3982
3995
  isOccludingIndex(index: number): boolean;
3983
3996
  setCullMaskAt(pos: Vector3, mask: number): Promise<void>;
3984
- /** Recomputes section masks and returns changed per-block masks. Omit indices to visit every cell. */
3985
- updateCullMasks(indices: Iterable<number> | undefined, occludes: (x: number, y: number, z: number) => boolean): [number, number][];
3997
+ /**
3998
+ * Recomputes section masks and returns changed per-block masks. Omit indices to visit every cell.
3999
+ * `culling` supplies neighbor face rules; `blockAt` supplies visible states outside this chunk.
4000
+ */
4001
+ 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][];
3986
4002
  /** Updates hidden faces for a cell's section entry or individual render object. */
3987
4003
  setCullMaskIndex(index: number, mask: number): Promise<void>;
3988
4004
  /** Rebuilds visible section geometry and discards results superseded by another rebuild or clear. */
@@ -1312,6 +1312,15 @@ interface MineRenderDatasets {
1312
1312
  untinted?: number[];
1313
1313
  biome?: BiomeColorKind;
1314
1314
  }>;
1315
+ blockCulling: {
1316
+ profiles: {
1317
+ type: string;
1318
+ group?: string;
1319
+ kind?: string;
1320
+ }[];
1321
+ blocks: Record<string, number>;
1322
+ groups?: Record<string, string[]>;
1323
+ };
1315
1324
  fluids: Record<string, {
1316
1325
  kind: "water" | "lava";
1317
1326
  renderModel: boolean;
@@ -3897,6 +3906,8 @@ interface WorldBlockIntersection {
3897
3906
  distance: number;
3898
3907
  }
3899
3908
 
3909
+ type BlockCullingData = MineRenderDatasets["blockCulling"];
3910
+
3900
3911
  /**
3901
3912
  * A 16×16×16 block section and its render objects.
3902
3913
  * Constructor coordinates identify the section. Block accessors use world block coordinates
@@ -3973,6 +3984,8 @@ declare class Chunk<SectionMeshing extends boolean = false> {
3973
3984
  isBlockVisibleAt(pos: Vector3): boolean;
3974
3985
  /** Reports whether a cell has a visible render object or section entry. */
3975
3986
  isBlockVisibleIndex(index: number): boolean;
3987
+ /** Stored state of a rendered, visible cell, or `undefined`. */
3988
+ visibleBlockIndex(index: number): Maybe<Pick<Block, "type" | "properties">>;
3976
3989
  /** Fluid kind bits and level of a cell, or 0. Hidden cells report 0. */
3977
3990
  fluidByteIndex(index: number): number;
3978
3991
  /** Whether the cell holds a fluid, merged or per-block. */
@@ -3981,8 +3994,11 @@ declare class Chunk<SectionMeshing extends boolean = false> {
3981
3994
  /** Reports whether a visible cell hides neighboring cube faces. */
3982
3995
  isOccludingIndex(index: number): boolean;
3983
3996
  setCullMaskAt(pos: Vector3, mask: number): Promise<void>;
3984
- /** Recomputes section masks and returns changed per-block masks. Omit indices to visit every cell. */
3985
- updateCullMasks(indices: Iterable<number> | undefined, occludes: (x: number, y: number, z: number) => boolean): [number, number][];
3997
+ /**
3998
+ * Recomputes section masks and returns changed per-block masks. Omit indices to visit every cell.
3999
+ * `culling` supplies neighbor face rules; `blockAt` supplies visible states outside this chunk.
4000
+ */
4001
+ 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][];
3986
4002
  /** Updates hidden faces for a cell's section entry or individual render object. */
3987
4003
  setCullMaskIndex(index: number, mask: number): Promise<void>;
3988
4004
  /** Rebuilds visible section geometry and discards results superseded by another rebuild or clear. */
@@ -1733,6 +1733,7 @@ ${key}` : key, async () => {
1733
1733
  if (!object(data)) return false;
1734
1734
  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)));
1735
1735
  if (dataset === "legacyBlocks") return object(data.blocks) && Object.values(data.blocks).every((value) => typeof value === "string");
1736
+ 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")));
1736
1737
  return Object.values(data).every((value) => {
1737
1738
  if (!object(value)) return false;
1738
1739
  switch (dataset) {
@@ -17834,6 +17835,38 @@ function sampleSectionLight(light, index) {
17834
17835
  };
17835
17836
  }
17836
17837
 
17838
+ // src/world/_culling/BlockCulling.ts
17839
+ init_Assets();
17840
+ init_MineRenderData();
17841
+ init_CubeFace();
17842
+ async function loadBlockCulling(root) {
17843
+ const { manifest } = await MineRenderData.resolve(root);
17844
+ return manifest.datasets.blockCulling ? MineRenderData.get("blockCulling", root) : void 0;
17845
+ }
17846
+ function blockCullingRule(data, type) {
17847
+ if (!type.includes(":")) type = `${DEFAULT_NAMESPACE}:${type}`;
17848
+ return Object.prototype.hasOwnProperty.call(data.blocks, type) ? data.profiles[data.blocks[type]] : void 0;
17849
+ }
17850
+ function skipsBlockFace(data, rule, block, neighbor, face) {
17851
+ const type = block.type.includes(":") ? block.type : `${DEFAULT_NAMESPACE}:${block.type}`;
17852
+ const other = neighbor.type.includes(":") ? neighbor.type : `${DEFAULT_NAMESPACE}:${neighbor.type}`;
17853
+ const same = type === other;
17854
+ const vertical = CUBE_FACES[face] === "up" /* UP */ || CUBE_FACES[face] === "down" /* DOWN */;
17855
+ switch (rule.type) {
17856
+ case "same_block":
17857
+ return same;
17858
+ case "same_block_vertical":
17859
+ return same && vertical;
17860
+ case "connected": {
17861
+ if (vertical) return same;
17862
+ const group = rule.group ? data.groups?.[rule.group] : void 0;
17863
+ return (same || !!group?.includes(type) && group.includes(other)) && String(block.properties?.[CUBE_FACES[face]]) === "true" && String(neighbor.properties?.[CUBE_FACES[face ^ 1]]) === "true";
17864
+ }
17865
+ default:
17866
+ return false;
17867
+ }
17868
+ }
17869
+
17837
17870
  // src/world/MineRenderWorld.ts
17838
17871
  var MineRenderWorld = class {
17839
17872
  constructor(scene, options = {}) {
@@ -18222,15 +18255,23 @@ var MineRenderWorld = class {
18222
18255
  return this.culling ??= Promise.resolve().then(async () => {
18223
18256
  let sliceStart = performance.now();
18224
18257
  try {
18258
+ const culling = await loadBlockCulling();
18225
18259
  while (this.pendingCulling.size) {
18226
18260
  const batch = [...this.pendingCulling];
18227
18261
  this.pendingCulling.clear();
18228
18262
  for (const [chunk, indices] of batch) {
18229
18263
  const neighbors = CUBE_FACE_OFFSETS.map(([x, y, z]) => this._chunks.get(`${chunk.x + x}_${chunk.y + y}_${chunk.z + z}`));
18230
- const masks = chunk.updateCullMasks(indices === "all" ? void 0 : indices, (x, y, z) => {
18264
+ const neighborAt = (x, y, z) => {
18231
18265
  const lx = x - chunk.x * 16, ly = y - chunk.y * 16, lz = z - chunk.z * 16;
18232
18266
  const section = neighbors[lx >= 16 ? 0 : lx < 0 ? 1 : ly >= 16 ? 2 : ly < 0 ? 3 : lz >= 16 ? 4 : 5];
18233
- return section?.isOccludingIndex((ly & 15) * 256 + (lz & 15) * 16 + (lx & 15)) ?? false;
18267
+ return [section, (ly & 15) * 256 + (lz & 15) * 16 + (lx & 15)];
18268
+ };
18269
+ const masks = chunk.updateCullMasks(indices === "all" ? void 0 : indices, (x, y, z) => {
18270
+ const [section, i] = neighborAt(x, y, z);
18271
+ return section?.isOccludingIndex(i) ?? false;
18272
+ }, culling, (x, y, z) => {
18273
+ const [section, i] = neighborAt(x, y, z);
18274
+ return section?.visibleBlockIndex(i);
18234
18275
  });
18235
18276
  const fluidIndices = indices === "all" ? function* () {
18236
18277
  for (let index = 0; index < 4096; index++) yield index;
@@ -19346,6 +19387,10 @@ var Chunk = class _Chunk {
19346
19387
  isBlockVisibleIndex(index) {
19347
19388
  return this.renderedBlocks.has(index) && !this.hiddenBlocks.has(index);
19348
19389
  }
19390
+ /** Stored state of a rendered, visible cell, or `undefined`. */
19391
+ visibleBlockIndex(index) {
19392
+ return this.isBlockVisibleIndex(index) ? this.data.stateAt(this.data.idAt(index)) : void 0;
19393
+ }
19349
19394
  /** Fluid kind bits and level of a cell, or 0. Hidden cells report 0. */
19350
19395
  fluidByteIndex(index) {
19351
19396
  return this.hiddenBlocks.has(index) ? 0 : this.fluidCells[index];
@@ -19364,18 +19409,30 @@ var Chunk = class _Chunk {
19364
19409
  async setCullMaskAt(pos, mask) {
19365
19410
  await this.setCullMaskIndex(_Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos)), mask);
19366
19411
  }
19367
- /** Recomputes section masks and returns changed per-block masks. Omit indices to visit every cell. */
19368
- updateCullMasks(indices, occludes) {
19412
+ /**
19413
+ * Recomputes section masks and returns changed per-block masks. Omit indices to visit every cell.
19414
+ * `culling` supplies neighbor face rules; `blockAt` supplies visible states outside this chunk.
19415
+ */
19416
+ updateCullMasks(indices, occludes, culling, blockAt) {
19369
19417
  const changed = [];
19418
+ const rules = [];
19370
19419
  for (const index of indices ?? this.fluidCells.keys()) {
19371
19420
  if (!this.isBlockVisibleIndex(index)) continue;
19372
19421
  const x = index % 16, y = Math.floor(index / 256), z = Math.floor(index / 16) % 16;
19422
+ const id = this.data.idAt(index);
19423
+ let rule = rules[id];
19424
+ if (culling && rule === void 0) rules[id] = rule = blockCullingRule(culling, this.data.stateAt(id).type) ?? null;
19425
+ const block = rule ? this.data.stateAt(id) : void 0;
19373
19426
  let mask = 0;
19374
19427
  for (let face = 0; face < CUBE_FACE_OFFSETS.length; face++) {
19375
19428
  const [dx, dy, dz] = CUBE_FACE_OFFSETS[face];
19376
19429
  const nx = x + dx, ny = y + dy, nz = z + dz;
19377
19430
  const inner = nx >= 0 && nx < 16 && ny >= 0 && ny < 16 && nz >= 0 && nz < 16;
19378
19431
  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;
19432
+ else if (rule) {
19433
+ const neighbor = inner ? this.visibleBlockIndex(ny * 256 + nz * 16 + nx) : blockAt?.(this.x * 16 + nx, this.y * 16 + ny, this.z * 16 + nz);
19434
+ if (neighbor && skipsBlockFace(culling, rule, block, neighbor, face)) mask |= 1 << face;
19435
+ }
19379
19436
  }
19380
19437
  if (this.fluidCells[index]) this.meshDirty = true;
19381
19438
  const entries = this.sectionBlocks.get(index);