minecraft-renderer 0.1.90 → 0.1.92

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.
Files changed (35) hide show
  1. package/dist/mesher.js +76 -76
  2. package/dist/mesher.js.map +4 -4
  3. package/dist/mesherWasm.js +202 -202
  4. package/dist/minecraft-renderer.js +67 -67
  5. package/dist/minecraft-renderer.js.meta.json +1 -1
  6. package/dist/threeWorker.js +424 -424
  7. package/package.json +1 -1
  8. package/src/graphicsBackend/config.ts +3 -0
  9. package/src/graphicsBackend/types.ts +8 -1
  10. package/src/lib/worldrendererCommon.ts +4 -1
  11. package/src/mesher-shared/exportedGeometryTypes.ts +1 -0
  12. package/src/mesher-shared/models.ts +10 -0
  13. package/src/mesher-shared/occludingBlocks.ts +53 -0
  14. package/src/mesher-shared/shared.ts +2 -0
  15. package/src/mesher-shared/tests/visGraph.test.ts +62 -0
  16. package/src/mesher-shared/visGraph.ts +155 -0
  17. package/src/mesher-shared/visibilitySet.ts +124 -0
  18. package/src/three/chunkMeshManager.ts +103 -4
  19. package/src/three/entities.ts +4 -1
  20. package/src/three/globalBlockBuffer.ts +6 -0
  21. package/src/three/globalLegacyBuffer.ts +6 -0
  22. package/src/three/occlusion/sectionOcclusionCull.ts +90 -0
  23. package/src/three/occlusion/sectionOcclusionGraph.ts +272 -0
  24. package/src/three/positionalAudioAttenuation.ts +35 -0
  25. package/src/three/tests/chunkMeshManagerLegacy.test.ts +7 -7
  26. package/src/three/tests/positionalAudioAttenuation.test.ts +51 -0
  27. package/src/three/tests/pr3PerFrameCpuCuts.test.ts +9 -9
  28. package/src/three/tests/sectionOcclusionCull.test.ts +51 -0
  29. package/src/three/tests/sectionOcclusionGraph.test.ts +134 -0
  30. package/src/three/tests/threeJsSound.test.ts +110 -0
  31. package/src/three/threeJsSound.ts +29 -11
  32. package/src/three/worldRendererThree.ts +30 -6
  33. package/src/wasm-mesher/bridge/convertChunk.ts +5 -51
  34. package/src/wasm-mesher/tests/sectionVisibilityFromWorld.test.ts +108 -0
  35. package/src/wasm-mesher/worker/mesherWasm.ts +13 -2
@@ -1,8 +1,8 @@
1
1
  //@ts-nocheck
2
2
  import { Vec3 } from 'vec3'
3
3
  import MinecraftData from 'minecraft-data'
4
- import PrismarineBlockLoader from 'prismarine-block'
5
4
  import moreBlockDataGeneratedJson from '../../lib/moreBlockDataGenerated.json'
5
+ import { getOccludingBlockMeta } from '../../mesher-shared/occludingBlocks'
6
6
 
7
7
  type BlockMeta = {
8
8
  invisibleBlocks: Uint16Array
@@ -10,6 +10,7 @@ type BlockMeta = {
10
10
  noAoBlocks: Uint16Array
11
11
  cullIdenticalBlocks: Uint16Array
12
12
  occludingBlocks: Uint16Array
13
+ occludingLookup: Uint8Array
13
14
  }
14
15
 
15
16
  const metaCache = new Map<string, BlockMeta>()
@@ -22,38 +23,6 @@ const blockToIds = (block: { minStateId: number; maxStateId: number }) => {
22
23
  return ids
23
24
  }
24
25
 
25
- const isCube = (shapes: any) => {
26
- if (!shapes || shapes.length !== 1) return false
27
- const s = shapes[0]
28
- return s[0] === 0 && s[1] === 0 && s[2] === 0 && s[3] === 1 && s[4] === 1 && s[5] === 1
29
- }
30
-
31
- const isLikelyFullCubeBlockName = (name: string) => {
32
- if (!name) return false
33
- if (name.includes('stairs')) return false
34
- if (name.includes('slab')) return false
35
- if (name.includes('fence')) return false
36
- if (name.includes('gate')) return false
37
- if (name.includes('pane')) return false
38
- if (name.includes('wall')) return false
39
- if (name.includes('door')) return false
40
- if (name.includes('trapdoor')) return false
41
- if (name.includes('sign')) return false
42
- if (name.includes('banner')) return false
43
- if (name.includes('rail')) return false
44
- if (name.includes('torch')) return false
45
- if (name.includes('lantern')) return false
46
- if (name.includes('button')) return false
47
- if (name.includes('lever')) return false
48
- if (name.includes('pressure_plate')) return false
49
- if (name.includes('carpet')) return false
50
- if (name.includes('flower')) return false
51
- if (name.includes('sapling')) return false
52
- if (name.includes('tall_grass')) return false
53
- if (name === 'grass' || name === 'short_grass' || name === 'tall_grass') return false
54
- return true
55
- }
56
-
57
26
  export const getBlockMeta = (version: string): BlockMeta => {
58
27
  const cached = metaCache.get(version)
59
28
  if (cached) return cached
@@ -64,30 +33,15 @@ export const getBlockMeta = (version: string): BlockMeta => {
64
33
  const transparentBlocks = new Uint16Array(mcData.blocksArray.filter(x => x.transparent).flatMap(blockToIds))
65
34
  const noAoBlocks = new Uint16Array(mcData.blocksArray.filter(x => moreBlockDataGeneratedJson.noOcclusions[x.name]).flatMap(blockToIds))
66
35
  const cullIdenticalBlocks = new Uint16Array(mcData.blocksArray.filter(x => x.name.includes('glass') || x.name.includes('ice')).flatMap(blockToIds))
67
- const Block = PrismarineBlockLoader(version)
68
- const noOcclusionsSet = new Set(Object.keys(moreBlockDataGeneratedJson.noOcclusions))
69
-
70
- const occludingBlockIds: number[] = []
71
- for (const idStr of Object.keys((mcData as any).blocksByStateId)) {
72
- const id = Number(idStr)
73
- if (!id) continue
74
- const b = (Block as any).fromStateId(id, 0)
75
- if (!b) continue
76
- if (b.transparent) continue
77
- if (b.boundingBox !== 'block') continue
78
- if (noOcclusionsSet.has(b.name)) continue
79
- if (!isCube(b.shapes)) continue
80
- occludingBlockIds.push(id)
81
- }
82
-
83
- const occludingBlocks = new Uint16Array(occludingBlockIds)
36
+ const { occludingBlocks, occludingLookup } = getOccludingBlockMeta(version)
84
37
 
85
38
  const meta = {
86
39
  invisibleBlocks,
87
40
  transparentBlocks,
88
41
  noAoBlocks,
89
42
  cullIdenticalBlocks,
90
- occludingBlocks
43
+ occludingBlocks,
44
+ occludingLookup
91
45
  }
92
46
 
93
47
  metaCache.set(version, meta)
@@ -0,0 +1,108 @@
1
+ //@ts-nocheck
2
+ import { test, expect, beforeEach } from 'vitest'
3
+ import MinecraftData from 'minecraft-data'
4
+ import blocksAtlasesJson from 'mc-assets/dist/blocksAtlases.json'
5
+ import blockStatesModels from 'mc-assets/dist/blockStatesModels.json'
6
+ import Chunks from 'prismarine-chunk'
7
+ import { Vec3 } from 'vec3'
8
+ import { setBlockStatesData } from '../../mesher-shared/models'
9
+ import { getOccludingBlockMeta } from '../../mesher-shared/occludingBlocks'
10
+ import { VisGraph, packVisibilitySet } from '../../mesher-shared/visGraph'
11
+ import { Direction, unpackVisibilitySet } from '../../mesher-shared/visibilitySet'
12
+ import { World } from '../../mesher-shared/world'
13
+
14
+ const VERSION = '1.18.2'
15
+ const SECTION_HEIGHT = 16
16
+
17
+ beforeEach(() => {
18
+ const mcData = MinecraftData(VERSION)
19
+ setBlockStatesData(blockStatesModels, blocksAtlasesJson, false, true, VERSION, { blocks: mcData.blocksArray })
20
+ })
21
+
22
+ function buildVisibilityFromWorld(world: World, sx: number, sy: number, sz: number, sectionHeight: number): number {
23
+ const { occludingLookup } = getOccludingBlockMeta(VERSION)
24
+ const graph = new VisGraph()
25
+ const cursor = new Vec3(0, 0, 0)
26
+ for (cursor.y = sy; cursor.y < sy + sectionHeight; cursor.y++) {
27
+ for (cursor.z = sz; cursor.z < sz + 16; cursor.z++) {
28
+ for (cursor.x = sx; cursor.x < sx + 16; cursor.x++) {
29
+ const block = world.getBlock(cursor)
30
+ if (block && occludingLookup[block.stateId]) {
31
+ graph.setOpaque(cursor.x - sx, cursor.y - sy, cursor.z - sz)
32
+ }
33
+ }
34
+ }
35
+ }
36
+ return packVisibilitySet(graph.resolve())
37
+ }
38
+
39
+ function solidStoneSectionWorld(): World {
40
+ const mcData = MinecraftData(VERSION)
41
+ const stoneId = mcData.blocksByName.stone!.defaultState
42
+ const Chunk = Chunks(VERSION) as any
43
+ const chunk = new Chunk(undefined as any)
44
+ for (let y = 0; y < SECTION_HEIGHT; y++) {
45
+ for (let z = 0; z < 16; z++) {
46
+ for (let x = 0; x < 16; x++) {
47
+ chunk.setBlockStateId(new Vec3(x, y, z), stoneId)
48
+ }
49
+ }
50
+ }
51
+ const world = new World(VERSION)
52
+ world.addColumn(0, 0, chunk.toJson())
53
+ return world
54
+ }
55
+
56
+ test('occludingLookup VisGraph: solid 16³ interior packs no face pairs (not all-open)', () => {
57
+ const world = solidStoneSectionWorld()
58
+ const bits = buildVisibilityFromWorld(world, 0, 0, 0, SECTION_HEIGHT)
59
+ const vs = unpackVisibilitySet(bits)
60
+ for (let a = 0; a < 6; a++) {
61
+ for (let b = 0; b < 6; b++) {
62
+ if (a === b) continue
63
+ expect(vs.visibilityBetween(a, b)).toBe(false)
64
+ }
65
+ }
66
+ expect(vs.visibilityBetween(Direction.NORTH, Direction.SOUTH)).toBe(false)
67
+ })
68
+
69
+ test('occludingLookup VisGraph: empty section is all-open', () => {
70
+ const world = new World(VERSION)
71
+ const Chunk = Chunks(VERSION) as any
72
+ world.addColumn(0, 0, new Chunk(undefined as any).toJson())
73
+ const bits = buildVisibilityFromWorld(world, 0, 0, 0, SECTION_HEIGHT)
74
+ const vs = unpackVisibilitySet(bits)
75
+ expect(vs.visibilityBetween(Direction.NORTH, Direction.SOUTH)).toBe(true)
76
+ })
77
+
78
+ test('occludingLookup VisGraph: tunnel N↔S only → only N/S connected', () => {
79
+ const mcData = MinecraftData(VERSION)
80
+ const stoneId = mcData.blocksByName.stone!.defaultState
81
+ const Chunk = Chunks(VERSION) as any
82
+ const chunk = new Chunk(undefined as any)
83
+ for (let y = 0; y < SECTION_HEIGHT; y++) {
84
+ for (let z = 0; z < 16; z++) {
85
+ for (let x = 0; x < 16; x++) {
86
+ if (x === 0 || x === 15) {
87
+ chunk.setBlockStateId(new Vec3(x, y, z), stoneId)
88
+ }
89
+ }
90
+ }
91
+ }
92
+ const world = new World(VERSION)
93
+ world.addColumn(0, 0, chunk.toJson())
94
+ const bits = buildVisibilityFromWorld(world, 0, 0, 0, SECTION_HEIGHT)
95
+ const vs = unpackVisibilitySet(bits)
96
+ expect(vs.visibilityBetween(Direction.NORTH, Direction.SOUTH)).toBe(true)
97
+ expect(vs.visibilityBetween(Direction.EAST, Direction.WEST)).toBe(false)
98
+ expect(vs.visibilityBetween(Direction.NORTH, Direction.EAST)).toBe(false)
99
+ })
100
+
101
+ test('occludingLookup: stone is occluding, air is not', () => {
102
+ const mcData = MinecraftData(VERSION)
103
+ const { occludingLookup } = getOccludingBlockMeta(VERSION)
104
+ const stoneId = mcData.blocksByName.stone!.defaultState
105
+ const airId = mcData.blocksByName.air!.defaultState
106
+ expect(occludingLookup[stoneId]).toBe(1)
107
+ expect(occludingLookup[airId]).toBe(0)
108
+ })
@@ -4,6 +4,8 @@ import { convertChunkToWasm, getBlockMeta, type ChunkConversionResult } from '..
4
4
  import { extractColumnHeightmap, splitColumnWasmOutputToSections } from '../bridge/render-from-wasm'
5
5
  import { setBlockStatesData as setMesherData } from '../../mesher-shared/models'
6
6
  import { defaultMesherConfig, type MesherGeometryOutput, SECTION_HEIGHT } from '../../mesher-shared/shared'
7
+ import { packVisibilitySet, VISIBILITY_SET_ALL_TRUE } from '../../mesher-shared/visibilitySet'
8
+ import { VisGraph } from '../../mesher-shared/visGraph'
7
9
  import { worldColumnKey, World } from '../../mesher-shared/world'
8
10
  import { handleGetHeightmap, EMPTY_COLUMN_HEIGHTMAP_SENTINEL } from '../../mesher-shared/computeHeightmap'
9
11
  import { collectBlockEntityMetadata, type SignMeta, type HeadMeta, type BannerMeta } from '../../mesher-shared/blockEntityMetadata'
@@ -1077,7 +1079,8 @@ function makeEmptyColumnGeometry(sx: number, sy: number, sz: number, sectionHeig
1077
1079
  signs: {},
1078
1080
  banners: {},
1079
1081
  hadErrors,
1080
- blocksCount: 0
1082
+ blocksCount: 0,
1083
+ visibilitySet: VISIBILITY_SET_ALL_TRUE
1081
1084
  }
1082
1085
  }
1083
1086
 
@@ -1465,16 +1468,22 @@ function processColumnTick() {
1465
1468
  const banners: Record<string, BannerMeta> = {}
1466
1469
  const beTarget = { signs, heads, banners }
1467
1470
  const beOpts = { disableBlockEntityTextures: world.config.disableBlockEntityTextures }
1471
+ const { occludingLookup } = getBlockMeta(version)
1472
+ const visGraph = new VisGraph()
1468
1473
  const cursor = new Vec3(0, 0, 0)
1469
1474
  for (cursor.y = sy; cursor.y < sy + sectionHeight; cursor.y++) {
1470
1475
  for (cursor.z = sz; cursor.z < sz + 16; cursor.z++) {
1471
1476
  for (cursor.x = sx; cursor.x < sx + 16; cursor.x++) {
1472
1477
  const b = world.getBlock(cursor)
1473
1478
  if (!b) continue
1479
+ if (occludingLookup[b.stateId]) {
1480
+ visGraph.setOpaque(cursor.x - sx, cursor.y - sy, cursor.z - sz)
1481
+ }
1474
1482
  collectBlockEntityMetadata(b, cursor.x, cursor.y, cursor.z, beTarget, beOpts, world)
1475
1483
  }
1476
1484
  }
1477
1485
  }
1486
+ const sectionVisibilitySet = packVisibilitySet(visGraph.resolve())
1478
1487
 
1479
1488
  let geometry: MesherGeometryOutput
1480
1489
  let transferable: any[] = []
@@ -1516,7 +1525,8 @@ function processColumnTick() {
1516
1525
  // `B:` debug overlay stat) and matches the per-section path's
1517
1526
  // semantics: number of blocks that contributed faces to this
1518
1527
  // section's geometry.
1519
- blocksCount: sectionBlocksCount
1528
+ blocksCount: sectionBlocksCount,
1529
+ visibilitySet: sectionVisibilitySet
1520
1530
  }
1521
1531
  if (exported.shaderCubes) {
1522
1532
  geometry.shaderCubes = {
@@ -1573,6 +1583,7 @@ function processColumnTick() {
1573
1583
  }
1574
1584
  } else {
1575
1585
  geometry = makeEmptyColumnGeometry(sx, sy, sz, sectionHeight, false)
1586
+ geometry.visibilitySet = sectionVisibilitySet
1576
1587
  // Still attach block entity metadata so the main thread sees
1577
1588
  // signs/heads/banners even for empty-mesh sections.
1578
1589
  geometry.signs = signs