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.
- package/dist/mesher.js +76 -76
- package/dist/mesher.js.map +4 -4
- package/dist/mesherWasm.js +202 -202
- package/dist/minecraft-renderer.js +67 -67
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +424 -424
- package/package.json +1 -1
- package/src/graphicsBackend/config.ts +3 -0
- package/src/graphicsBackend/types.ts +8 -1
- package/src/lib/worldrendererCommon.ts +4 -1
- package/src/mesher-shared/exportedGeometryTypes.ts +1 -0
- package/src/mesher-shared/models.ts +10 -0
- package/src/mesher-shared/occludingBlocks.ts +53 -0
- package/src/mesher-shared/shared.ts +2 -0
- package/src/mesher-shared/tests/visGraph.test.ts +62 -0
- package/src/mesher-shared/visGraph.ts +155 -0
- package/src/mesher-shared/visibilitySet.ts +124 -0
- package/src/three/chunkMeshManager.ts +103 -4
- package/src/three/entities.ts +4 -1
- package/src/three/globalBlockBuffer.ts +6 -0
- package/src/three/globalLegacyBuffer.ts +6 -0
- package/src/three/occlusion/sectionOcclusionCull.ts +90 -0
- package/src/three/occlusion/sectionOcclusionGraph.ts +272 -0
- package/src/three/positionalAudioAttenuation.ts +35 -0
- package/src/three/tests/chunkMeshManagerLegacy.test.ts +7 -7
- package/src/three/tests/positionalAudioAttenuation.test.ts +51 -0
- package/src/three/tests/pr3PerFrameCpuCuts.test.ts +9 -9
- package/src/three/tests/sectionOcclusionCull.test.ts +51 -0
- package/src/three/tests/sectionOcclusionGraph.test.ts +134 -0
- package/src/three/tests/threeJsSound.test.ts +110 -0
- package/src/three/threeJsSound.ts +29 -11
- package/src/three/worldRendererThree.ts +30 -6
- package/src/wasm-mesher/bridge/convertChunk.ts +5 -51
- package/src/wasm-mesher/tests/sectionVisibilityFromWorld.test.ts +108 -0
- 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
|
|
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
|