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';
@@ -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;
@@ -1422,6 +1431,12 @@ declare class DisplayTransforms {
1422
1431
  static withHandFallbacks(display: Model["display"]): Model["display"];
1423
1432
  /** Build a display pose for geometry already centered at the model's [8, 8, 8] pivot. */
1424
1433
  static getMatrix(display: Model["display"], position: DisplayPosition): Matrix4;
1434
+ /** Position, rotation, and scale of a display pose for geometry already centered at the model's [8, 8, 8] pivot. */
1435
+ static getTransform(display: Model["display"], position: DisplayPosition): {
1436
+ position: Vector3;
1437
+ quaternion: Quaternion;
1438
+ scale: Vector3;
1439
+ };
1425
1440
  /** Transforms geometry in place and reverses triangle winding when the matrix reflects it. */
1426
1441
  static apply(geometry: BufferGeometry, matrix: Matrix4): void;
1427
1442
  }
@@ -3889,6 +3904,8 @@ interface WorldBlockIntersection {
3889
3904
  distance: number;
3890
3905
  }
3891
3906
 
3907
+ type BlockCullingData = MineRenderDatasets["blockCulling"];
3908
+
3892
3909
  /**
3893
3910
  * A 16×16×16 block section and its render objects.
3894
3911
  * Constructor coordinates identify the section. Block accessors use world block coordinates
@@ -3965,6 +3982,8 @@ declare class Chunk<SectionMeshing extends boolean = false> {
3965
3982
  isBlockVisibleAt(pos: Vector3): boolean;
3966
3983
  /** Reports whether a cell has a visible render object or section entry. */
3967
3984
  isBlockVisibleIndex(index: number): boolean;
3985
+ /** Stored state of a rendered, visible cell, or `undefined`. */
3986
+ visibleBlockIndex(index: number): Maybe<Pick<Block, "type" | "properties">>;
3968
3987
  /** Fluid kind bits and level of a cell, or 0. Hidden cells report 0. */
3969
3988
  fluidByteIndex(index: number): number;
3970
3989
  /** Whether the cell holds a fluid, merged or per-block. */
@@ -3973,8 +3992,11 @@ declare class Chunk<SectionMeshing extends boolean = false> {
3973
3992
  /** Reports whether a visible cell hides neighboring cube faces. */
3974
3993
  isOccludingIndex(index: number): boolean;
3975
3994
  setCullMaskAt(pos: Vector3, mask: number): Promise<void>;
3976
- /** Recomputes section masks and returns changed per-block masks. Omit indices to visit every cell. */
3977
- 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][];
3978
4000
  /** Updates hidden faces for a cell's section entry or individual render object. */
3979
4001
  setCullMaskIndex(index: number, mask: number): Promise<void>;
3980
4002
  /** 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';
@@ -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;
@@ -1422,6 +1431,12 @@ declare class DisplayTransforms {
1422
1431
  static withHandFallbacks(display: Model["display"]): Model["display"];
1423
1432
  /** Build a display pose for geometry already centered at the model's [8, 8, 8] pivot. */
1424
1433
  static getMatrix(display: Model["display"], position: DisplayPosition): Matrix4;
1434
+ /** Position, rotation, and scale of a display pose for geometry already centered at the model's [8, 8, 8] pivot. */
1435
+ static getTransform(display: Model["display"], position: DisplayPosition): {
1436
+ position: Vector3;
1437
+ quaternion: Quaternion;
1438
+ scale: Vector3;
1439
+ };
1425
1440
  /** Transforms geometry in place and reverses triangle winding when the matrix reflects it. */
1426
1441
  static apply(geometry: BufferGeometry, matrix: Matrix4): void;
1427
1442
  }
@@ -3889,6 +3904,8 @@ interface WorldBlockIntersection {
3889
3904
  distance: number;
3890
3905
  }
3891
3906
 
3907
+ type BlockCullingData = MineRenderDatasets["blockCulling"];
3908
+
3892
3909
  /**
3893
3910
  * A 16×16×16 block section and its render objects.
3894
3911
  * Constructor coordinates identify the section. Block accessors use world block coordinates
@@ -3965,6 +3982,8 @@ declare class Chunk<SectionMeshing extends boolean = false> {
3965
3982
  isBlockVisibleAt(pos: Vector3): boolean;
3966
3983
  /** Reports whether a cell has a visible render object or section entry. */
3967
3984
  isBlockVisibleIndex(index: number): boolean;
3985
+ /** Stored state of a rendered, visible cell, or `undefined`. */
3986
+ visibleBlockIndex(index: number): Maybe<Pick<Block, "type" | "properties">>;
3968
3987
  /** Fluid kind bits and level of a cell, or 0. Hidden cells report 0. */
3969
3988
  fluidByteIndex(index: number): number;
3970
3989
  /** Whether the cell holds a fluid, merged or per-block. */
@@ -3973,8 +3992,11 @@ declare class Chunk<SectionMeshing extends boolean = false> {
3973
3992
  /** Reports whether a visible cell hides neighboring cube faces. */
3974
3993
  isOccludingIndex(index: number): boolean;
3975
3994
  setCullMaskAt(pos: Vector3, mask: number): Promise<void>;
3976
- /** Recomputes section masks and returns changed per-block masks. Omit indices to visit every cell. */
3977
- 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][];
3978
4000
  /** Updates hidden faces for a cell's section entry or individual render object. */
3979
4001
  setCullMaskIndex(index: number, mask: number): Promise<void>;
3980
4002
  /** Rebuilds visible section geometry and discards results superseded by another rebuild or clear. */
@@ -1479,6 +1479,11 @@ var init_DisplayTransforms = __esm({
1479
1479
  }
1480
1480
  /** Build a display pose for geometry already centered at the model's [8, 8, 8] pivot. */
1481
1481
  static getMatrix(display, position) {
1482
+ const transform = this.getTransform(display, position);
1483
+ return new import_three7.Matrix4().compose(transform.position, transform.quaternion, transform.scale);
1484
+ }
1485
+ /** Position, rotation, and scale of a display pose for geometry already centered at the model's [8, 8, 8] pivot. */
1486
+ static getTransform(display, position) {
1482
1487
  const transform = this.withHandFallbacks(display)?.[position] ?? {};
1483
1488
  const left = position === "firstperson_lefthand" /* FIRSTPERSON_LEFTHAND */ || position === "thirdperson_lefthand" /* THIRDPERSON_LEFTHAND */;
1484
1489
  const sign = left ? -1 : 1;
@@ -1487,7 +1492,7 @@ var init_DisplayTransforms = __esm({
1487
1492
  const translation = new import_three7.Vector3(...transform.translation ?? [0, 0, 0]).clampScalar(-80, 80);
1488
1493
  translation.x *= sign;
1489
1494
  const scale = new import_three7.Vector3(...transform.scale ?? [1, 1, 1]).clampScalar(-4, 4);
1490
- return new import_three7.Matrix4().compose(translation, rotation, scale);
1495
+ return { position: translation, quaternion: rotation, scale };
1491
1496
  }
1492
1497
  /** Transforms geometry in place and reverses triangle winding when the matrix reflects it. */
1493
1498
  static apply(geometry, matrix) {
@@ -1672,6 +1677,7 @@ ${key}` : key, async () => {
1672
1677
  if (!object(data)) return false;
1673
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)));
1674
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")));
1675
1681
  return Object.values(data).every((value) => {
1676
1682
  if (!object(value)) return false;
1677
1683
  switch (dataset) {
@@ -7007,16 +7013,21 @@ var init_ModelObject = __esm({
7007
7013
  const special = this.originalModel.special;
7008
7014
  if (special) {
7009
7015
  const parts2 = await SpecialItems.getParts(special, this.originalModel.key?.root, this.originalModel.components);
7010
- const transform = this.options.displayPosition ? DisplayTransforms.getMatrix(this.originalModel.display, this.options.displayPosition) : new import_three27.Matrix4();
7011
- transform.multiply(new import_three27.Matrix4().makeTranslation(-8, -8, -8));
7016
+ const display = new SceneObject();
7017
+ if (this.options.displayPosition) {
7018
+ const transform = DisplayTransforms.getTransform(this.originalModel.display, this.options.displayPosition);
7019
+ display.position.copy(transform.position);
7020
+ display.quaternion.copy(transform.quaternion);
7021
+ display.scale.copy(transform.scale);
7022
+ }
7023
+ this.add(display);
7024
+ const offset = new import_three27.Matrix4().makeTranslation(-8, -8, -8);
7012
7025
  try {
7013
7026
  const targets = [];
7014
7027
  for (const part of parts2) {
7015
7028
  const object4 = new EntityObject(part.model, { flip: false, wireframe: this.options.wireframe, tints: part.tints, faces: part.faces });
7016
- object4.matrix.copy(transform).multiply(part.transform);
7017
- object4.matrixWorldNeedsUpdate = true;
7018
- object4.matrixAutoUpdate = false;
7019
- this.add(object4);
7029
+ object4.matrix.multiplyMatrices(offset, part.transform).decompose(object4.position, object4.quaternion, object4.scale);
7030
+ display.add(object4);
7020
7031
  await object4.init(part.material);
7021
7032
  object4.iterateAllMeshes((mesh) => {
7022
7033
  const source = mesh.material;
@@ -17789,6 +17800,38 @@ function sampleSectionLight(light, index) {
17789
17800
  };
17790
17801
  }
17791
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
+
17792
17835
  // src/world/MineRenderWorld.ts
17793
17836
  var MineRenderWorld = class {
17794
17837
  constructor(scene, options = {}) {
@@ -18177,15 +18220,23 @@ var MineRenderWorld = class {
18177
18220
  return this.culling ?? (this.culling = Promise.resolve().then(async () => {
18178
18221
  let sliceStart = performance.now();
18179
18222
  try {
18223
+ const culling = await loadBlockCulling();
18180
18224
  while (this.pendingCulling.size) {
18181
18225
  const batch = [...this.pendingCulling];
18182
18226
  this.pendingCulling.clear();
18183
18227
  for (const [chunk, indices] of batch) {
18184
18228
  const neighbors = CUBE_FACE_OFFSETS.map(([x, y, z]) => this._chunks.get(`${chunk.x + x}_${chunk.y + y}_${chunk.z + z}`));
18185
- const masks = chunk.updateCullMasks(indices === "all" ? void 0 : indices, (x, y, z) => {
18229
+ const neighborAt = (x, y, z) => {
18186
18230
  const lx = x - chunk.x * 16, ly = y - chunk.y * 16, lz = z - chunk.z * 16;
18187
18231
  const section = neighbors[lx >= 16 ? 0 : lx < 0 ? 1 : ly >= 16 ? 2 : ly < 0 ? 3 : lz >= 16 ? 4 : 5];
18188
- 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);
18189
18240
  });
18190
18241
  const fluidIndices = indices === "all" ? function* () {
18191
18242
  for (let index = 0; index < 4096; index++) yield index;
@@ -19300,6 +19351,10 @@ var Chunk = class _Chunk {
19300
19351
  isBlockVisibleIndex(index) {
19301
19352
  return this.renderedBlocks.has(index) && !this.hiddenBlocks.has(index);
19302
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
+ }
19303
19358
  /** Fluid kind bits and level of a cell, or 0. Hidden cells report 0. */
19304
19359
  fluidByteIndex(index) {
19305
19360
  return this.hiddenBlocks.has(index) ? 0 : this.fluidCells[index];
@@ -19318,18 +19373,30 @@ var Chunk = class _Chunk {
19318
19373
  async setCullMaskAt(pos, mask) {
19319
19374
  await this.setCullMaskIndex(_Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos)), mask);
19320
19375
  }
19321
- /** Recomputes section masks and returns changed per-block masks. Omit indices to visit every cell. */
19322
- 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) {
19323
19381
  const changed = [];
19382
+ const rules = [];
19324
19383
  for (const index of indices ?? this.fluidCells.keys()) {
19325
19384
  if (!this.isBlockVisibleIndex(index)) continue;
19326
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;
19327
19390
  let mask = 0;
19328
19391
  for (let face = 0; face < CUBE_FACE_OFFSETS.length; face++) {
19329
19392
  const [dx, dy, dz] = CUBE_FACE_OFFSETS[face];
19330
19393
  const nx = x + dx, ny = y + dy, nz = z + dz;
19331
19394
  const inner = nx >= 0 && nx < 16 && ny >= 0 && ny < 16 && nz >= 0 && nz < 16;
19332
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
+ }
19333
19400
  }
19334
19401
  if (this.fluidCells[index]) this.meshDirty = true;
19335
19402
  const entries = this.sectionBlocks.get(index);