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.
@@ -1,5 +1,5 @@
1
1
  import * as three from 'three';
2
- import { Vector3, PixelFormat, BoxGeometry, Material, ColorRepresentation, MeshBasicMaterial, Mesh, Euler, Vector2, BufferAttribute, Float32BufferAttribute, BufferGeometry, Object3D, Box3, InstancedMesh, PerspectiveCamera, OrthographicCamera, Matrix4, Box2, Scene, Object3DEventMap, ShaderMaterial, Texture, Camera, MOUSE, TOUCH, Controls, WebGLRenderer, EventDispatcher, Raycaster, Intersection, Group, DataTexture, CanvasTexture, InterleavedBufferAttribute, TrianglesDrawModes, Line, Points } from 'three';
2
+ import { Vector3, PixelFormat, BoxGeometry, Material, ColorRepresentation, MeshBasicMaterial, Mesh, Euler, Vector2, BufferAttribute, Float32BufferAttribute, BufferGeometry, Object3D, Box3, InstancedMesh, PerspectiveCamera, OrthographicCamera, Matrix4, Quaternion, Box2, Scene, Object3DEventMap, ShaderMaterial, Texture, Camera, MOUSE, TOUCH, Controls, WebGLRenderer, EventDispatcher, Raycaster, Intersection, Group, DataTexture, CanvasTexture, InterleavedBufferAttribute, TrianglesDrawModes, Line, Points } from 'three';
3
3
  import * as nodeCanvas from 'canvas';
4
4
  import { ImageData as ImageData$1, CanvasRenderingContext2D } from 'canvas';
5
5
  import { Buffer } from 'buffer';
@@ -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;
@@ -1424,6 +1433,12 @@ declare class DisplayTransforms {
1424
1433
  static withHandFallbacks(display: Model["display"]): Model["display"];
1425
1434
  /** Build a display pose for geometry already centered at the model's [8, 8, 8] pivot. */
1426
1435
  static getMatrix(display: Model["display"], position: DisplayPosition): Matrix4;
1436
+ /** Position, rotation, and scale of a display pose for geometry already centered at the model's [8, 8, 8] pivot. */
1437
+ static getTransform(display: Model["display"], position: DisplayPosition): {
1438
+ position: Vector3;
1439
+ quaternion: Quaternion;
1440
+ scale: Vector3;
1441
+ };
1427
1442
  /** Transforms geometry in place and reverses triangle winding when the matrix reflects it. */
1428
1443
  static apply(geometry: BufferGeometry, matrix: Matrix4): void;
1429
1444
  }
@@ -3891,6 +3906,8 @@ interface WorldBlockIntersection {
3891
3906
  distance: number;
3892
3907
  }
3893
3908
 
3909
+ type BlockCullingData = MineRenderDatasets["blockCulling"];
3910
+
3894
3911
  /**
3895
3912
  * A 16×16×16 block section and its render objects.
3896
3913
  * Constructor coordinates identify the section. Block accessors use world block coordinates
@@ -3967,6 +3984,8 @@ declare class Chunk<SectionMeshing extends boolean = false> {
3967
3984
  isBlockVisibleAt(pos: Vector3): boolean;
3968
3985
  /** Reports whether a cell has a visible render object or section entry. */
3969
3986
  isBlockVisibleIndex(index: number): boolean;
3987
+ /** Stored state of a rendered, visible cell, or `undefined`. */
3988
+ visibleBlockIndex(index: number): Maybe<Pick<Block, "type" | "properties">>;
3970
3989
  /** Fluid kind bits and level of a cell, or 0. Hidden cells report 0. */
3971
3990
  fluidByteIndex(index: number): number;
3972
3991
  /** Whether the cell holds a fluid, merged or per-block. */
@@ -3975,8 +3994,11 @@ declare class Chunk<SectionMeshing extends boolean = false> {
3975
3994
  /** Reports whether a visible cell hides neighboring cube faces. */
3976
3995
  isOccludingIndex(index: number): boolean;
3977
3996
  setCullMaskAt(pos: Vector3, mask: number): Promise<void>;
3978
- /** Recomputes section masks and returns changed per-block masks. Omit indices to visit every cell. */
3979
- 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][];
3980
4002
  /** Updates hidden faces for a cell's section entry or individual render object. */
3981
4003
  setCullMaskIndex(index: number, mask: number): Promise<void>;
3982
4004
  /** Rebuilds visible section geometry and discards results superseded by another rebuild or clear. */
@@ -1,5 +1,5 @@
1
1
  import * as three from 'three';
2
- import { Vector3, PixelFormat, BoxGeometry, Material, ColorRepresentation, MeshBasicMaterial, Mesh, Euler, Vector2, BufferAttribute, Float32BufferAttribute, BufferGeometry, Object3D, Box3, InstancedMesh, PerspectiveCamera, OrthographicCamera, Matrix4, Box2, Scene, Object3DEventMap, ShaderMaterial, Texture, Camera, MOUSE, TOUCH, Controls, WebGLRenderer, EventDispatcher, Raycaster, Intersection, Group, DataTexture, CanvasTexture, InterleavedBufferAttribute, TrianglesDrawModes, Line, Points } from 'three';
2
+ import { Vector3, PixelFormat, BoxGeometry, Material, ColorRepresentation, MeshBasicMaterial, Mesh, Euler, Vector2, BufferAttribute, Float32BufferAttribute, BufferGeometry, Object3D, Box3, InstancedMesh, PerspectiveCamera, OrthographicCamera, Matrix4, Quaternion, Box2, Scene, Object3DEventMap, ShaderMaterial, Texture, Camera, MOUSE, TOUCH, Controls, WebGLRenderer, EventDispatcher, Raycaster, Intersection, Group, DataTexture, CanvasTexture, InterleavedBufferAttribute, TrianglesDrawModes, Line, Points } from 'three';
3
3
  import * as nodeCanvas from 'canvas';
4
4
  import { ImageData as ImageData$1, CanvasRenderingContext2D } from 'canvas';
5
5
  import { Buffer } from 'buffer';
@@ -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;
@@ -1424,6 +1433,12 @@ declare class DisplayTransforms {
1424
1433
  static withHandFallbacks(display: Model["display"]): Model["display"];
1425
1434
  /** Build a display pose for geometry already centered at the model's [8, 8, 8] pivot. */
1426
1435
  static getMatrix(display: Model["display"], position: DisplayPosition): Matrix4;
1436
+ /** Position, rotation, and scale of a display pose for geometry already centered at the model's [8, 8, 8] pivot. */
1437
+ static getTransform(display: Model["display"], position: DisplayPosition): {
1438
+ position: Vector3;
1439
+ quaternion: Quaternion;
1440
+ scale: Vector3;
1441
+ };
1427
1442
  /** Transforms geometry in place and reverses triangle winding when the matrix reflects it. */
1428
1443
  static apply(geometry: BufferGeometry, matrix: Matrix4): void;
1429
1444
  }
@@ -3891,6 +3906,8 @@ interface WorldBlockIntersection {
3891
3906
  distance: number;
3892
3907
  }
3893
3908
 
3909
+ type BlockCullingData = MineRenderDatasets["blockCulling"];
3910
+
3894
3911
  /**
3895
3912
  * A 16×16×16 block section and its render objects.
3896
3913
  * Constructor coordinates identify the section. Block accessors use world block coordinates
@@ -3967,6 +3984,8 @@ declare class Chunk<SectionMeshing extends boolean = false> {
3967
3984
  isBlockVisibleAt(pos: Vector3): boolean;
3968
3985
  /** Reports whether a cell has a visible render object or section entry. */
3969
3986
  isBlockVisibleIndex(index: number): boolean;
3987
+ /** Stored state of a rendered, visible cell, or `undefined`. */
3988
+ visibleBlockIndex(index: number): Maybe<Pick<Block, "type" | "properties">>;
3970
3989
  /** Fluid kind bits and level of a cell, or 0. Hidden cells report 0. */
3971
3990
  fluidByteIndex(index: number): number;
3972
3991
  /** Whether the cell holds a fluid, merged or per-block. */
@@ -3975,8 +3994,11 @@ declare class Chunk<SectionMeshing extends boolean = false> {
3975
3994
  /** Reports whether a visible cell hides neighboring cube faces. */
3976
3995
  isOccludingIndex(index: number): boolean;
3977
3996
  setCullMaskAt(pos: Vector3, mask: number): Promise<void>;
3978
- /** Recomputes section masks and returns changed per-block masks. Omit indices to visit every cell. */
3979
- 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][];
3980
4002
  /** Updates hidden faces for a cell's section entry or individual render object. */
3981
4003
  setCullMaskIndex(index: number, mask: number): Promise<void>;
3982
4004
  /** Rebuilds visible section geometry and discards results superseded by another rebuild or clear. */
@@ -1535,6 +1535,11 @@ var init_DisplayTransforms = __esm({
1535
1535
  }
1536
1536
  /** Build a display pose for geometry already centered at the model's [8, 8, 8] pivot. */
1537
1537
  static getMatrix(display, position) {
1538
+ const transform = this.getTransform(display, position);
1539
+ return new import_three7.Matrix4().compose(transform.position, transform.quaternion, transform.scale);
1540
+ }
1541
+ /** Position, rotation, and scale of a display pose for geometry already centered at the model's [8, 8, 8] pivot. */
1542
+ static getTransform(display, position) {
1538
1543
  const transform = this.withHandFallbacks(display)?.[position] ?? {};
1539
1544
  const left = position === "firstperson_lefthand" /* FIRSTPERSON_LEFTHAND */ || position === "thirdperson_lefthand" /* THIRDPERSON_LEFTHAND */;
1540
1545
  const sign = left ? -1 : 1;
@@ -1543,7 +1548,7 @@ var init_DisplayTransforms = __esm({
1543
1548
  const translation = new import_three7.Vector3(...transform.translation ?? [0, 0, 0]).clampScalar(-80, 80);
1544
1549
  translation.x *= sign;
1545
1550
  const scale = new import_three7.Vector3(...transform.scale ?? [1, 1, 1]).clampScalar(-4, 4);
1546
- return new import_three7.Matrix4().compose(translation, rotation, scale);
1551
+ return { position: translation, quaternion: rotation, scale };
1547
1552
  }
1548
1553
  /** Transforms geometry in place and reverses triangle winding when the matrix reflects it. */
1549
1554
  static apply(geometry, matrix) {
@@ -1728,6 +1733,7 @@ ${key}` : key, async () => {
1728
1733
  if (!object(data)) return false;
1729
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)));
1730
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")));
1731
1737
  return Object.values(data).every((value) => {
1732
1738
  if (!object(value)) return false;
1733
1739
  switch (dataset) {
@@ -7109,16 +7115,21 @@ var init_ModelObject = __esm({
7109
7115
  const special = this.originalModel.special;
7110
7116
  if (special) {
7111
7117
  const parts2 = await SpecialItems.getParts(special, this.originalModel.key?.root, this.originalModel.components);
7112
- const transform = this.options.displayPosition ? DisplayTransforms.getMatrix(this.originalModel.display, this.options.displayPosition) : new import_three27.Matrix4();
7113
- transform.multiply(new import_three27.Matrix4().makeTranslation(-8, -8, -8));
7118
+ const display = new SceneObject();
7119
+ if (this.options.displayPosition) {
7120
+ const transform = DisplayTransforms.getTransform(this.originalModel.display, this.options.displayPosition);
7121
+ display.position.copy(transform.position);
7122
+ display.quaternion.copy(transform.quaternion);
7123
+ display.scale.copy(transform.scale);
7124
+ }
7125
+ this.add(display);
7126
+ const offset = new import_three27.Matrix4().makeTranslation(-8, -8, -8);
7114
7127
  try {
7115
7128
  const targets = [];
7116
7129
  for (const part of parts2) {
7117
7130
  const object4 = new EntityObject(part.model, { flip: false, wireframe: this.options.wireframe, tints: part.tints, faces: part.faces });
7118
- object4.matrix.copy(transform).multiply(part.transform);
7119
- object4.matrixWorldNeedsUpdate = true;
7120
- object4.matrixAutoUpdate = false;
7121
- this.add(object4);
7131
+ object4.matrix.multiplyMatrices(offset, part.transform).decompose(object4.position, object4.quaternion, object4.scale);
7132
+ display.add(object4);
7122
7133
  await object4.init(part.material);
7123
7134
  object4.iterateAllMeshes((mesh) => {
7124
7135
  const source = mesh.material;
@@ -17824,6 +17835,38 @@ function sampleSectionLight(light, index) {
17824
17835
  };
17825
17836
  }
17826
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
+
17827
17870
  // src/world/MineRenderWorld.ts
17828
17871
  var MineRenderWorld = class {
17829
17872
  constructor(scene, options = {}) {
@@ -18212,15 +18255,23 @@ var MineRenderWorld = class {
18212
18255
  return this.culling ??= Promise.resolve().then(async () => {
18213
18256
  let sliceStart = performance.now();
18214
18257
  try {
18258
+ const culling = await loadBlockCulling();
18215
18259
  while (this.pendingCulling.size) {
18216
18260
  const batch = [...this.pendingCulling];
18217
18261
  this.pendingCulling.clear();
18218
18262
  for (const [chunk, indices] of batch) {
18219
18263
  const neighbors = CUBE_FACE_OFFSETS.map(([x, y, z]) => this._chunks.get(`${chunk.x + x}_${chunk.y + y}_${chunk.z + z}`));
18220
- const masks = chunk.updateCullMasks(indices === "all" ? void 0 : indices, (x, y, z) => {
18264
+ const neighborAt = (x, y, z) => {
18221
18265
  const lx = x - chunk.x * 16, ly = y - chunk.y * 16, lz = z - chunk.z * 16;
18222
18266
  const section = neighbors[lx >= 16 ? 0 : lx < 0 ? 1 : ly >= 16 ? 2 : ly < 0 ? 3 : lz >= 16 ? 4 : 5];
18223
- 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);
18224
18275
  });
18225
18276
  const fluidIndices = indices === "all" ? function* () {
18226
18277
  for (let index = 0; index < 4096; index++) yield index;
@@ -19336,6 +19387,10 @@ var Chunk = class _Chunk {
19336
19387
  isBlockVisibleIndex(index) {
19337
19388
  return this.renderedBlocks.has(index) && !this.hiddenBlocks.has(index);
19338
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
+ }
19339
19394
  /** Fluid kind bits and level of a cell, or 0. Hidden cells report 0. */
19340
19395
  fluidByteIndex(index) {
19341
19396
  return this.hiddenBlocks.has(index) ? 0 : this.fluidCells[index];
@@ -19354,18 +19409,30 @@ var Chunk = class _Chunk {
19354
19409
  async setCullMaskAt(pos, mask) {
19355
19410
  await this.setCullMaskIndex(_Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos)), mask);
19356
19411
  }
19357
- /** Recomputes section masks and returns changed per-block masks. Omit indices to visit every cell. */
19358
- 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) {
19359
19417
  const changed = [];
19418
+ const rules = [];
19360
19419
  for (const index of indices ?? this.fluidCells.keys()) {
19361
19420
  if (!this.isBlockVisibleIndex(index)) continue;
19362
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;
19363
19426
  let mask = 0;
19364
19427
  for (let face = 0; face < CUBE_FACE_OFFSETS.length; face++) {
19365
19428
  const [dx, dy, dz] = CUBE_FACE_OFFSETS[face];
19366
19429
  const nx = x + dx, ny = y + dy, nz = z + dz;
19367
19430
  const inner = nx >= 0 && nx < 16 && ny >= 0 && ny < 16 && nz >= 0 && nz < 16;
19368
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
+ }
19369
19436
  }
19370
19437
  if (this.fluidCells[index]) this.meshDirty = true;
19371
19438
  const entries = this.sectionBlocks.get(index);