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
package/package.json
CHANGED
|
@@ -21,6 +21,9 @@ export const defaultWorldRendererConfig = {
|
|
|
21
21
|
debugModelVariant: undefined as undefined | number[],
|
|
22
22
|
futuristicReveal: false,
|
|
23
23
|
|
|
24
|
+
/** Master toggle for section occlusion graph (smart / cave cull). Disabled in spectator. */
|
|
25
|
+
smartCull: true,
|
|
26
|
+
|
|
24
27
|
// Performance settings
|
|
25
28
|
wasmMesher: true,
|
|
26
29
|
/** Render full 1×1 cubes through the instanced shader path (requires WebGL2). */
|
|
@@ -18,7 +18,14 @@ import { WorldRendererConfig } from './config'
|
|
|
18
18
|
export type MaybePromise<T> = Promise<T> | T
|
|
19
19
|
|
|
20
20
|
export interface SoundSystem {
|
|
21
|
-
playSound: (
|
|
21
|
+
playSound: (
|
|
22
|
+
position: { x: number; y: number; z: number },
|
|
23
|
+
path: string,
|
|
24
|
+
volume?: number,
|
|
25
|
+
pitch?: number,
|
|
26
|
+
timeout?: number,
|
|
27
|
+
attenuationDistance?: number
|
|
28
|
+
) => void
|
|
22
29
|
destroy: () => void
|
|
23
30
|
}
|
|
24
31
|
|
|
@@ -54,7 +54,10 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
54
54
|
chunksRenderBelowEnabled: false,
|
|
55
55
|
chunksRenderDistanceOverride: undefined as number | undefined,
|
|
56
56
|
chunksRenderDistanceEnabled: false,
|
|
57
|
-
disableEntities: false
|
|
57
|
+
disableEntities: false,
|
|
58
|
+
/** Tint section borders by occlusion BFS step (perf debug overlay). */
|
|
59
|
+
caveCullingDebug: false,
|
|
60
|
+
smartCull: true
|
|
58
61
|
// disableParticles: false
|
|
59
62
|
})
|
|
60
63
|
|
|
@@ -11,6 +11,8 @@ import { MesherGeometryOutput, HighestBlockInfo } from './shared'
|
|
|
11
11
|
import { collectBlockEntityMetadata } from './blockEntityMetadata'
|
|
12
12
|
import { preflatBlockCalculation, resolveBlockPropertiesForMeshing } from './blockPropertiesForMeshing'
|
|
13
13
|
import { faceIsCulled, roundCardinalDir } from './faceOcclusion'
|
|
14
|
+
import { getOccludingBlockMeta } from './occludingBlocks'
|
|
15
|
+
import { packVisibilitySet, VisGraph } from './visGraph'
|
|
14
16
|
|
|
15
17
|
export { preflatBlockCalculation, resolveBlockPropertiesForMeshing } from './blockPropertiesForMeshing'
|
|
16
18
|
|
|
@@ -540,12 +542,18 @@ export function getSectionGeometry(sx: number, sy: number, sz: number, world: Wo
|
|
|
540
542
|
indicesCount: 0
|
|
541
543
|
}
|
|
542
544
|
|
|
545
|
+
const { occludingLookup } = getOccludingBlockMeta(world.config.version)
|
|
546
|
+
const visGraph = new VisGraph()
|
|
547
|
+
|
|
543
548
|
const cursor = new Vec3(0, 0, 0)
|
|
544
549
|
for (cursor.y = sy; cursor.y < sy + readHeight; cursor.y++) {
|
|
545
550
|
for (cursor.z = sz; cursor.z < sz + 16; cursor.z++) {
|
|
546
551
|
for (cursor.x = sx; cursor.x < sx + 16; cursor.x++) {
|
|
547
552
|
let block = world.getBlock(cursor, blockProvider, attr)!
|
|
548
553
|
if (INVISIBLE_BLOCKS.has(block.name)) continue
|
|
554
|
+
if (occludingLookup[block.stateId]) {
|
|
555
|
+
visGraph.setOpaque(cursor.x - sx, cursor.y - sy, cursor.z - sz)
|
|
556
|
+
}
|
|
549
557
|
collectBlockEntityMetadata(block, cursor.x, cursor.y, cursor.z, attr, { disableBlockEntityTextures: world.config.disableBlockEntityTextures }, world)
|
|
550
558
|
const biome = block.biome.name
|
|
551
559
|
|
|
@@ -680,6 +688,8 @@ export function getSectionGeometry(sx: number, sy: number, sz: number, world: Wo
|
|
|
680
688
|
delete attr.uvs
|
|
681
689
|
}
|
|
682
690
|
|
|
691
|
+
attr.visibilitySet = packVisibilitySet(visGraph.resolve())
|
|
692
|
+
|
|
683
693
|
return attr
|
|
684
694
|
}
|
|
685
695
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import MinecraftData from 'minecraft-data'
|
|
3
|
+
import PrismarineBlockLoader from 'prismarine-block'
|
|
4
|
+
import moreBlockDataGeneratedJson from '../lib/moreBlockDataGenerated.json'
|
|
5
|
+
|
|
6
|
+
export type OccludingBlockMeta = {
|
|
7
|
+
occludingBlocks: Uint16Array
|
|
8
|
+
occludingLookup: Uint8Array
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const metaCache = new Map<string, OccludingBlockMeta>()
|
|
12
|
+
|
|
13
|
+
const isCube = (shapes: unknown) => {
|
|
14
|
+
if (!shapes || !Array.isArray(shapes) || shapes.length !== 1) return false
|
|
15
|
+
const s = shapes[0] as number[]
|
|
16
|
+
return s[0] === 0 && s[1] === 0 && s[2] === 0 && s[3] === 1 && s[4] === 1 && s[5] === 1
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Opaque full-cube state ids used for face culling and VisGraph — single source of truth. */
|
|
20
|
+
export const getOccludingBlockMeta = (version: string): OccludingBlockMeta => {
|
|
21
|
+
const cached = metaCache.get(version)
|
|
22
|
+
if (cached) return cached
|
|
23
|
+
|
|
24
|
+
const mcData = MinecraftData(version)
|
|
25
|
+
const Block = PrismarineBlockLoader(version)
|
|
26
|
+
const noOcclusionsSet = new Set(Object.keys(moreBlockDataGeneratedJson.noOcclusions))
|
|
27
|
+
|
|
28
|
+
const occludingBlockIds: number[] = []
|
|
29
|
+
for (const idStr of Object.keys((mcData as { blocksByStateId: Record<string, unknown> }).blocksByStateId)) {
|
|
30
|
+
const id = Number(idStr)
|
|
31
|
+
if (!id) continue
|
|
32
|
+
const b = (
|
|
33
|
+
Block as { fromStateId: (id: number, y: number) => { transparent?: boolean; boundingBox?: string; name: string; shapes?: unknown } | null }
|
|
34
|
+
).fromStateId(id, 0)
|
|
35
|
+
if (!b) continue
|
|
36
|
+
if (b.transparent) continue
|
|
37
|
+
if (b.boundingBox !== 'block') continue
|
|
38
|
+
if (noOcclusionsSet.has(b.name)) continue
|
|
39
|
+
if (!isCube(b.shapes)) continue
|
|
40
|
+
occludingBlockIds.push(id)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const occludingBlocks = new Uint16Array(occludingBlockIds)
|
|
44
|
+
const maxId = occludingBlockIds.length ? Math.max(...occludingBlockIds) : 0
|
|
45
|
+
const occludingLookup = new Uint8Array(maxId + 1)
|
|
46
|
+
for (const id of occludingBlockIds) {
|
|
47
|
+
occludingLookup[id] = 1
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const meta = { occludingBlocks, occludingLookup }
|
|
51
|
+
metaCache.set(version, meta)
|
|
52
|
+
return meta
|
|
53
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { test, expect } from 'vitest'
|
|
3
|
+
import { Direction, VisibilitySet, packVisibilitySet, unpackVisibilitySet, visibilityBetweenPacked } from '../visibilitySet'
|
|
4
|
+
import { VisGraph, computeSectionVisibilitySet } from '../visGraph'
|
|
5
|
+
|
|
6
|
+
test('empty section → all face pairs visible', () => {
|
|
7
|
+
const bits = computeSectionVisibilitySet(16, () => false)
|
|
8
|
+
const vs = unpackVisibilitySet(bits)
|
|
9
|
+
for (let a = 0; a < 6; a++) {
|
|
10
|
+
for (let b = 0; b < 6; b++) {
|
|
11
|
+
if (a === b) continue
|
|
12
|
+
expect(vs.visibilityBetween(a, b)).toBe(true)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('solid stone cube → no face pairs', () => {
|
|
18
|
+
const bits = computeSectionVisibilitySet(16, () => true)
|
|
19
|
+
const vs = unpackVisibilitySet(bits)
|
|
20
|
+
for (let a = 0; a < 6; a++) {
|
|
21
|
+
for (let b = 0; b < 6; b++) {
|
|
22
|
+
if (a === b) continue
|
|
23
|
+
expect(vs.visibilityBetween(a, b)).toBe(false)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('tunnel through section N↔S only → only N/S connected', () => {
|
|
29
|
+
const bits = computeSectionVisibilitySet(16, (lx, _ly, lz) => {
|
|
30
|
+
// walls on east/west, open north-south corridor along z
|
|
31
|
+
return lx === 0 || lx === 15
|
|
32
|
+
})
|
|
33
|
+
const vs = unpackVisibilitySet(bits)
|
|
34
|
+
expect(vs.visibilityBetween(Direction.NORTH, Direction.SOUTH)).toBe(true)
|
|
35
|
+
expect(vs.visibilityBetween(Direction.EAST, Direction.WEST)).toBe(false)
|
|
36
|
+
expect(vs.visibilityBetween(Direction.NORTH, Direction.EAST)).toBe(false)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('VisibilitySet pack/unpack roundtrip', () => {
|
|
40
|
+
const vs = new VisibilitySet()
|
|
41
|
+
vs.add(new Set([Direction.NORTH, Direction.SOUTH]))
|
|
42
|
+
const packed = packVisibilitySet(vs)
|
|
43
|
+
const restored = unpackVisibilitySet(packed)
|
|
44
|
+
expect(restored.visibilityBetween(Direction.NORTH, Direction.SOUTH)).toBe(true)
|
|
45
|
+
expect(restored.visibilityBetween(Direction.EAST, Direction.WEST)).toBe(false)
|
|
46
|
+
expect(visibilityBetweenPacked(packed, Direction.NORTH, Direction.SOUTH)).toBe(true)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('VisGraph fast path: mostly opaque → all visible', () => {
|
|
50
|
+
const graph = new VisGraph()
|
|
51
|
+
let count = 0
|
|
52
|
+
for (let ly = 0; ly < 16 && count < 3841; ly++) {
|
|
53
|
+
for (let lz = 0; lz < 16 && count < 3841; lz++) {
|
|
54
|
+
for (let lx = 0; lx < 16 && count < 3841; lx++) {
|
|
55
|
+
graph.setOpaque(lx, ly, lz)
|
|
56
|
+
count++
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const vs = graph.resolve()
|
|
61
|
+
expect(vs.visibilityBetween(Direction.NORTH, Direction.SOUTH)).toBe(true)
|
|
62
|
+
})
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Port of Minecraft VisGraph — flood-fill air pockets in a 16³ section.
|
|
4
|
+
* @see extracted_minecraft_data/client/net/minecraft/client/renderer/chunk/VisGraph.java
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Direction, DIRECTIONS, VisibilitySet, packVisibilitySet } from './visibilitySet'
|
|
8
|
+
|
|
9
|
+
const LEN = 16
|
|
10
|
+
const SIZE = 4096
|
|
11
|
+
const DX = 1
|
|
12
|
+
const DZ = 16
|
|
13
|
+
const DY = 256
|
|
14
|
+
const INVALID_INDEX = -1
|
|
15
|
+
|
|
16
|
+
function getIndex(x: number, y: number, z: number): number {
|
|
17
|
+
return (x << 0) | (y << 8) | (z << 4)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const INDEX_OF_EDGES: number[] = (() => {
|
|
21
|
+
const edges: number[] = []
|
|
22
|
+
for (let x = 0; x < 16; x++) {
|
|
23
|
+
for (let z = 0; z < 16; z++) {
|
|
24
|
+
for (let y = 0; y < 16; y++) {
|
|
25
|
+
if (x === 0 || x === 15 || z === 0 || z === 15 || y === 0 || y === 15) {
|
|
26
|
+
edges.push(getIndex(x, y, z))
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return edges
|
|
32
|
+
})()
|
|
33
|
+
|
|
34
|
+
class BitSet4096 {
|
|
35
|
+
private readonly words = new Uint32Array(128)
|
|
36
|
+
|
|
37
|
+
get(index: number): boolean {
|
|
38
|
+
return ((this.words[index >> 5]! >> (index & 31)) & 1) === 1
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
set(index: number, value: boolean): void {
|
|
42
|
+
if (value) {
|
|
43
|
+
this.words[index >> 5]! |= 1 << (index & 31)
|
|
44
|
+
} else {
|
|
45
|
+
this.words[index >> 5]! &= ~(1 << (index & 31))
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export class VisGraph {
|
|
51
|
+
private readonly bitSet = new BitSet4096()
|
|
52
|
+
private empty = SIZE
|
|
53
|
+
|
|
54
|
+
setOpaque(x: number, y: number, z: number): void {
|
|
55
|
+
const index = getIndex(x & 15, y & 15, z & 15)
|
|
56
|
+
if (!this.bitSet.get(index)) {
|
|
57
|
+
this.bitSet.set(index, true)
|
|
58
|
+
this.empty--
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
resolve(): VisibilitySet {
|
|
63
|
+
const result = new VisibilitySet()
|
|
64
|
+
if (SIZE - this.empty < 256) {
|
|
65
|
+
result.setAll(true)
|
|
66
|
+
} else if (this.empty === 0) {
|
|
67
|
+
result.setAll(false)
|
|
68
|
+
} else {
|
|
69
|
+
for (const edgeIndex of INDEX_OF_EDGES) {
|
|
70
|
+
if (!this.bitSet.get(edgeIndex)) {
|
|
71
|
+
result.add(this.floodFill(edgeIndex))
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return result
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private floodFill(startIndex: number): Set<Direction> {
|
|
79
|
+
const faces = new Set<Direction>()
|
|
80
|
+
const queue: number[] = [startIndex]
|
|
81
|
+
let head = 0
|
|
82
|
+
this.bitSet.set(startIndex, true)
|
|
83
|
+
|
|
84
|
+
while (head < queue.length) {
|
|
85
|
+
const index = queue[head++]!
|
|
86
|
+
this.addEdges(index, faces)
|
|
87
|
+
|
|
88
|
+
for (const dir of DIRECTIONS) {
|
|
89
|
+
const neighbor = this.getNeighborIndexAtFace(index, dir)
|
|
90
|
+
if (neighbor >= 0 && !this.bitSet.get(neighbor)) {
|
|
91
|
+
this.bitSet.set(neighbor, true)
|
|
92
|
+
queue.push(neighbor)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return faces
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private addEdges(index: number, faces: Set<Direction>): void {
|
|
101
|
+
const x = (index >> 0) & 15
|
|
102
|
+
if (x === 0) faces.add(Direction.WEST)
|
|
103
|
+
else if (x === 15) faces.add(Direction.EAST)
|
|
104
|
+
|
|
105
|
+
const y = (index >> 8) & 15
|
|
106
|
+
if (y === 0) faces.add(Direction.DOWN)
|
|
107
|
+
else if (y === 15) faces.add(Direction.UP)
|
|
108
|
+
|
|
109
|
+
const z = (index >> 4) & 15
|
|
110
|
+
if (z === 0) faces.add(Direction.NORTH)
|
|
111
|
+
else if (z === 15) faces.add(Direction.SOUTH)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private getNeighborIndexAtFace(index: number, dir: Direction): number {
|
|
115
|
+
switch (dir) {
|
|
116
|
+
case Direction.DOWN:
|
|
117
|
+
if (((index >> 8) & 15) === 0) return INVALID_INDEX
|
|
118
|
+
return index - DY
|
|
119
|
+
case Direction.UP:
|
|
120
|
+
if (((index >> 8) & 15) === 15) return INVALID_INDEX
|
|
121
|
+
return index + DY
|
|
122
|
+
case Direction.NORTH:
|
|
123
|
+
if (((index >> 4) & 15) === 0) return INVALID_INDEX
|
|
124
|
+
return index - DZ
|
|
125
|
+
case Direction.SOUTH:
|
|
126
|
+
if (((index >> 4) & 15) === 15) return INVALID_INDEX
|
|
127
|
+
return index + DZ
|
|
128
|
+
case Direction.WEST:
|
|
129
|
+
if (((index >> 0) & 15) === 0) return INVALID_INDEX
|
|
130
|
+
return index - DX
|
|
131
|
+
case Direction.EAST:
|
|
132
|
+
if (((index >> 0) & 15) === 15) return INVALID_INDEX
|
|
133
|
+
return index + DX
|
|
134
|
+
default:
|
|
135
|
+
return INVALID_INDEX
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function computeSectionVisibilitySet(sectionHeight: number, isOpaqueAt: (lx: number, ly: number, lz: number) => boolean): number {
|
|
141
|
+
const graph = new VisGraph()
|
|
142
|
+
const height = Math.min(sectionHeight, LEN)
|
|
143
|
+
for (let ly = 0; ly < height; ly++) {
|
|
144
|
+
for (let lz = 0; lz < LEN; lz++) {
|
|
145
|
+
for (let lx = 0; lx < LEN; lx++) {
|
|
146
|
+
if (isOpaqueAt(lx, ly, lz)) {
|
|
147
|
+
graph.setOpaque(lx, ly, lz)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return packVisibilitySet(graph.resolve())
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export { packVisibilitySet } from './visibilitySet'
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Port of Minecraft VisibilitySet — 6×6 face connectivity matrix for section air pockets.
|
|
4
|
+
* @see extracted_minecraft_data/client/net/minecraft/client/renderer/chunk/VisibilitySet.java
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Java Direction.values() order: DOWN, UP, NORTH, SOUTH, WEST, EAST */
|
|
8
|
+
export enum Direction {
|
|
9
|
+
DOWN = 0,
|
|
10
|
+
UP = 1,
|
|
11
|
+
NORTH = 2,
|
|
12
|
+
SOUTH = 3,
|
|
13
|
+
WEST = 4,
|
|
14
|
+
EAST = 5
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const DIRECTIONS: readonly Direction[] = [Direction.DOWN, Direction.UP, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST]
|
|
18
|
+
|
|
19
|
+
const FACINGS = DIRECTIONS.length
|
|
20
|
+
|
|
21
|
+
/** All 36 bits set — fail-open default when visibility data is missing. */
|
|
22
|
+
export const VISIBILITY_SET_ALL_TRUE = 2 ** 36 - 1
|
|
23
|
+
|
|
24
|
+
export class VisibilitySet {
|
|
25
|
+
private data = new Uint8Array(Math.ceil((FACINGS * FACINGS) / 8))
|
|
26
|
+
|
|
27
|
+
add(faces: Set<Direction>): void {
|
|
28
|
+
for (const a of faces) {
|
|
29
|
+
for (const b of faces) {
|
|
30
|
+
this.set(a, b, true)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
set(a: Direction, b: Direction, value: boolean): void {
|
|
36
|
+
this.setBit(a + b * FACINGS, value)
|
|
37
|
+
this.setBit(b + a * FACINGS, value)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
setAll(value: boolean): void {
|
|
41
|
+
this.data.fill(value ? 0xff : 0)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
visibilityBetween(a: Direction, b: Direction): boolean {
|
|
45
|
+
return this.getBit(a + b * FACINGS)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static allTrue(): VisibilitySet {
|
|
49
|
+
const vs = new VisibilitySet()
|
|
50
|
+
vs.setAll(true)
|
|
51
|
+
return vs
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static allFalse(): VisibilitySet {
|
|
55
|
+
const vs = new VisibilitySet()
|
|
56
|
+
vs.setAll(false)
|
|
57
|
+
return vs
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private setBit(index: number, value: boolean): void {
|
|
61
|
+
const byteIndex = index >> 3
|
|
62
|
+
const bitIndex = index & 7
|
|
63
|
+
if (value) {
|
|
64
|
+
this.data[byteIndex]! |= 1 << bitIndex
|
|
65
|
+
} else {
|
|
66
|
+
this.data[byteIndex]! &= ~(1 << bitIndex)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private getBit(index: number): boolean {
|
|
71
|
+
const byteIndex = index >> 3
|
|
72
|
+
const bitIndex = index & 7
|
|
73
|
+
return ((this.data[byteIndex]! >> bitIndex) & 1) === 1
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function packVisibilitySet(vs: VisibilitySet): number {
|
|
78
|
+
let bits = 0
|
|
79
|
+
for (let a = 0; a < FACINGS; a++) {
|
|
80
|
+
for (let b = 0; b < FACINGS; b++) {
|
|
81
|
+
if (vs.visibilityBetween(a, b)) {
|
|
82
|
+
bits += 2 ** (a + b * FACINGS)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return bits
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function unpackVisibilitySet(bits: number): VisibilitySet {
|
|
90
|
+
const vs = new VisibilitySet()
|
|
91
|
+
for (let a = 0; a < FACINGS; a++) {
|
|
92
|
+
for (let b = 0; b < FACINGS; b++) {
|
|
93
|
+
const idx = a + b * FACINGS
|
|
94
|
+
if (Math.floor(bits / 2 ** idx) % 2 === 1) {
|
|
95
|
+
vs.set(a, b, true)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return vs
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function visibilityBetweenPacked(bits: number, a: Direction, b: Direction): boolean {
|
|
103
|
+
const idx = a + b * FACINGS
|
|
104
|
+
return Math.floor(bits / 2 ** idx) % 2 === 1
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function oppositeDirection(dir: Direction): Direction {
|
|
108
|
+
switch (dir) {
|
|
109
|
+
case Direction.DOWN:
|
|
110
|
+
return Direction.UP
|
|
111
|
+
case Direction.UP:
|
|
112
|
+
return Direction.DOWN
|
|
113
|
+
case Direction.NORTH:
|
|
114
|
+
return Direction.SOUTH
|
|
115
|
+
case Direction.SOUTH:
|
|
116
|
+
return Direction.NORTH
|
|
117
|
+
case Direction.WEST:
|
|
118
|
+
return Direction.EAST
|
|
119
|
+
case Direction.EAST:
|
|
120
|
+
return Direction.WEST
|
|
121
|
+
default:
|
|
122
|
+
return dir
|
|
123
|
+
}
|
|
124
|
+
}
|