minecraft-renderer 0.1.80 → 0.1.82
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/README.md +37 -1
- package/dist/mesher.js +19 -19
- package/dist/mesher.js.map +3 -3
- package/dist/mesherWasm.js +167 -167
- package/dist/minecraft-renderer.js +71 -71
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +432 -432
- package/package.json +1 -1
- package/src/lib/worldrendererCommon.ts +11 -0
- package/src/mesher-shared/models.ts +2 -30
- package/src/mesher-shared/modelsGeometryCommon.ts +22 -0
- package/src/mesher-shared/standaloneRenderer.ts +2 -5
- package/src/three/cameraCollisionBlockCache.ts +156 -0
- package/src/three/cameraCollisionSolidity.ts +50 -0
- package/src/three/chunkMeshManager.ts +4 -128
- package/src/three/globalLegacyBuffer.ts +0 -107
- package/src/three/sectionRaycastAabb.ts +0 -98
- package/src/three/tests/chunkMeshManagerLegacy.test.ts +0 -30
- package/src/three/thirdPersonVoxelRaycast.ts +120 -0
- package/src/three/worldRendererThree.ts +30 -40
- package/src/wasm-mesher/bridge/render-from-wasm.ts +3 -5
- package/src/wasm-mesher/tests/cameraCollisionBlockCache.test.ts +82 -0
- package/src/wasm-mesher/tests/sectionRaycastAabb.test.ts +1 -106
- package/src/wasm-mesher/tests/thirdPersonVoxelRaycast.test.ts +63 -0
package/package.json
CHANGED
|
@@ -32,6 +32,7 @@ import { generateSpiralMatrix } from './spiral'
|
|
|
32
32
|
import { PlayerStateReactive } from '../playerState/playerState'
|
|
33
33
|
import { IndexedData } from 'minecraft-data'
|
|
34
34
|
import { WorldRendererConfig } from '../graphicsBackend/config'
|
|
35
|
+
import { CameraCollisionBlockCache } from '../three/cameraCollisionBlockCache'
|
|
35
36
|
|
|
36
37
|
function mod(x, n) {
|
|
37
38
|
return ((x % n) + n) % n
|
|
@@ -43,6 +44,8 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
43
44
|
timeOfTheDay = 0
|
|
44
45
|
lastMesherSkyLight = 15
|
|
45
46
|
worldSizeParams = { minY: 0, worldHeight: 256 }
|
|
47
|
+
/** Block columns for third-person voxel DDA (renderer thread). */
|
|
48
|
+
cameraCollisionBlockCache: CameraCollisionBlockCache
|
|
46
49
|
reactiveDebugParams = proxy({
|
|
47
50
|
stopRendering: false,
|
|
48
51
|
chunksRenderAboveOverride: undefined as number | undefined,
|
|
@@ -216,6 +219,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
216
219
|
public displayOptions: DisplayWorldOptions,
|
|
217
220
|
public initOptions: GraphicsInitOptions
|
|
218
221
|
) {
|
|
222
|
+
this.cameraCollisionBlockCache = new CameraCollisionBlockCache(this.displayOptions.version)
|
|
219
223
|
this.snapshotInitialValues()
|
|
220
224
|
this.worldRendererConfig = displayOptions.inWorldRenderingConfig
|
|
221
225
|
this.playerStateReactive = displayOptions.playerStateReactive!
|
|
@@ -892,6 +896,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
892
896
|
`-> chunk ${JSON.stringify({ x, z, chunkLength: chunk.length, customBlockModelsLength: customBlockModels ? Object.keys(customBlockModels).length : 0 })}`
|
|
893
897
|
)
|
|
894
898
|
this.mesherLogReader?.chunkReceived(x, z, chunk.length)
|
|
899
|
+
this.cameraCollisionBlockCache.ingestColumn(x, z, chunk)
|
|
895
900
|
const sectionHeight = this.getSectionHeight()
|
|
896
901
|
const CHUNK_SIZE = 16
|
|
897
902
|
|
|
@@ -960,6 +965,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
960
965
|
delete this.finishedSections[sectionKey]
|
|
961
966
|
}
|
|
962
967
|
this.highestBlocksByChunks.delete(`${x},${z}`)
|
|
968
|
+
this.cameraCollisionBlockCache.removeColumn(x, z)
|
|
963
969
|
const heightmapKey = `${Math.floor(x / 16)},${Math.floor(z / 16)}`
|
|
964
970
|
delete this.reactiveState.world.heightmaps[heightmapKey]
|
|
965
971
|
|
|
@@ -1036,6 +1042,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
1036
1042
|
'loadChunk',
|
|
1037
1043
|
({ x, z, chunk, worldConfig, isLightUpdate }) => {
|
|
1038
1044
|
this.worldSizeParams = worldConfig
|
|
1045
|
+
this.cameraCollisionBlockCache.setWorldBounds(worldConfig.minY, worldConfig.worldHeight)
|
|
1039
1046
|
this.queuedChunks.add(`${x},${z}`)
|
|
1040
1047
|
const args = [x, z, chunk, isLightUpdate]
|
|
1041
1048
|
if (!currentLoadChunkBatch) {
|
|
@@ -1139,6 +1146,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
1139
1146
|
worldEmitter,
|
|
1140
1147
|
'onWorldSwitch',
|
|
1141
1148
|
() => {
|
|
1149
|
+
this.cameraCollisionBlockCache.clear()
|
|
1142
1150
|
for (const fn of this.onWorldSwitched) {
|
|
1143
1151
|
try {
|
|
1144
1152
|
fn()
|
|
@@ -1229,6 +1237,9 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
1229
1237
|
}
|
|
1230
1238
|
}
|
|
1231
1239
|
this.logWorkerWork(`-> blockUpdate ${JSON.stringify({ pos, stateId, customBlockModels })}`)
|
|
1240
|
+
if (stateId !== undefined) {
|
|
1241
|
+
this.cameraCollisionBlockCache.setBlockStateId(pos.x, pos.y, pos.z, stateId)
|
|
1242
|
+
}
|
|
1232
1243
|
this.setSectionDirty(pos, true, true)
|
|
1233
1244
|
if (this.neighborChunkUpdates) {
|
|
1234
1245
|
const CHUNK_SIZE = 16
|
|
@@ -4,7 +4,7 @@ import worldBlockProvider, { WorldBlockProvider } from 'mc-assets/dist/worldBloc
|
|
|
4
4
|
import moreBlockDataGeneratedJson from '../lib/moreBlockDataGenerated.json'
|
|
5
5
|
import { BlockType } from '../playground/shared'
|
|
6
6
|
import { World, BlockModelPartsResolved, WorldBlock as Block, WorldBlock, worldColumnKey } from './world'
|
|
7
|
-
import { BlockElement, buildRotationMatrix, elemFaces, matmul3, matmulmat3, vecadd3, vecsub3 } from './modelsGeometryCommon'
|
|
7
|
+
import { BlockElement, buildElementRotation, buildRotationMatrix, elemFaces, matmul3, matmulmat3, vecadd3, vecsub3 } from './modelsGeometryCommon'
|
|
8
8
|
import { getSideShading } from './vertexShading'
|
|
9
9
|
import { INVISIBLE_BLOCKS } from './worldConstants'
|
|
10
10
|
import { MesherGeometryOutput, HighestBlockInfo } from './shared'
|
|
@@ -342,35 +342,7 @@ function renderElement(
|
|
|
342
342
|
let localShift = null as any
|
|
343
343
|
|
|
344
344
|
if (element.rotation && !needTiles) {
|
|
345
|
-
|
|
346
|
-
localMatrix = buildRotationMatrix(element.rotation.axis, element.rotation.angle)
|
|
347
|
-
|
|
348
|
-
localShift = vecsub3(element.rotation.origin, matmul3(localMatrix, element.rotation.origin))
|
|
349
|
-
|
|
350
|
-
// Apply rescale if specified
|
|
351
|
-
if (element.rotation.rescale) {
|
|
352
|
-
const FIT_TO_BLOCK_SCALE_MULTIPLIER = 2 - Math.sqrt(2)
|
|
353
|
-
const angleRad = (element.rotation.angle * Math.PI) / 180
|
|
354
|
-
const scale = Math.abs(Math.sin(angleRad)) * FIT_TO_BLOCK_SCALE_MULTIPLIER
|
|
355
|
-
|
|
356
|
-
// Get axis vector components (1 for the rotation axis, 0 for others)
|
|
357
|
-
const axisX = element.rotation.axis === 'x' ? 1 : 0
|
|
358
|
-
const axisY = element.rotation.axis === 'y' ? 1 : 0
|
|
359
|
-
const axisZ = element.rotation.axis === 'z' ? 1 : 0
|
|
360
|
-
|
|
361
|
-
// Create scale matrix: scale = (1 - axisComponent) * scaleFactor + 1
|
|
362
|
-
const scaleMatrix = [
|
|
363
|
-
[(1 - axisX) * scale + 1, 0, 0],
|
|
364
|
-
[0, (1 - axisY) * scale + 1, 0],
|
|
365
|
-
[0, 0, (1 - axisZ) * scale + 1]
|
|
366
|
-
]
|
|
367
|
-
|
|
368
|
-
// Apply scaling to the transformation matrix
|
|
369
|
-
localMatrix = matmulmat3(localMatrix, scaleMatrix)
|
|
370
|
-
|
|
371
|
-
// Recalculate shift with the new matrix
|
|
372
|
-
localShift = vecsub3(element.rotation.origin, matmul3(localMatrix, element.rotation.origin))
|
|
373
|
-
}
|
|
345
|
+
;({ localMatrix, localShift } = buildElementRotation(element.rotation))
|
|
374
346
|
}
|
|
375
347
|
|
|
376
348
|
const aos: number[] = []
|
|
@@ -88,6 +88,28 @@ export function matmulmat3(a, b) {
|
|
|
88
88
|
return te
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
export function buildElementRotation(rotation) {
|
|
92
|
+
let localMatrix = buildRotationMatrix(rotation.axis, rotation.angle)
|
|
93
|
+
let localShift = vecsub3(rotation.origin, matmul3(localMatrix, rotation.origin))
|
|
94
|
+
|
|
95
|
+
if (rotation.rescale) {
|
|
96
|
+
const FIT_TO_BLOCK_SCALE_MULTIPLIER = 2 - Math.sqrt(2)
|
|
97
|
+
const angleRad = (rotation.angle * Math.PI) / 180
|
|
98
|
+
const scale = Math.abs(Math.sin(angleRad)) * FIT_TO_BLOCK_SCALE_MULTIPLIER
|
|
99
|
+
const axisX = rotation.axis === 'x' ? 1 : 0
|
|
100
|
+
const axisY = rotation.axis === 'y' ? 1 : 0
|
|
101
|
+
const axisZ = rotation.axis === 'z' ? 1 : 0
|
|
102
|
+
const scaleMatrix = [
|
|
103
|
+
[(1 - axisX) * scale + 1, 0, 0],
|
|
104
|
+
[0, (1 - axisY) * scale + 1, 0],
|
|
105
|
+
[0, 0, (1 - axisZ) * scale + 1]
|
|
106
|
+
]
|
|
107
|
+
localMatrix = matmulmat3(localMatrix, scaleMatrix)
|
|
108
|
+
localShift = vecsub3(rotation.origin, matmul3(localMatrix, rotation.origin))
|
|
109
|
+
}
|
|
110
|
+
return { localMatrix, localShift }
|
|
111
|
+
}
|
|
112
|
+
|
|
91
113
|
export const elemFaces = {
|
|
92
114
|
up: {
|
|
93
115
|
dir: [0, 1, 0],
|
|
@@ -5,7 +5,7 @@ import { Block } from 'prismarine-block'
|
|
|
5
5
|
import { IndexedData } from 'minecraft-data'
|
|
6
6
|
import * as THREE from 'three'
|
|
7
7
|
import { BlockModelPartsResolved } from './world'
|
|
8
|
-
import { BlockElement, buildRotationMatrix, elemFaces, matmul3, matmulmat3, vecadd3, vecsub3 } from './modelsGeometryCommon'
|
|
8
|
+
import { BlockElement, buildElementRotation, buildRotationMatrix, elemFaces, matmul3, matmulmat3, vecadd3, vecsub3 } from './modelsGeometryCommon'
|
|
9
9
|
|
|
10
10
|
type NeighborSide = 'up' | 'down' | 'east' | 'west' | 'north' | 'south'
|
|
11
11
|
|
|
@@ -78,10 +78,7 @@ function renderElement(element: BlockElement, doAO: boolean, attr, globalMatrix,
|
|
|
78
78
|
let localShift = null as any
|
|
79
79
|
|
|
80
80
|
if (element.rotation) {
|
|
81
|
-
|
|
82
|
-
localMatrix = buildRotationMatrix(element.rotation.axis, element.rotation.angle)
|
|
83
|
-
|
|
84
|
-
localShift = vecsub3(element.rotation.origin, matmul3(localMatrix, element.rotation.origin))
|
|
81
|
+
;({ localMatrix, localShift } = buildElementRotation(element.rotation))
|
|
85
82
|
}
|
|
86
83
|
|
|
87
84
|
const aos: number[] = []
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import Chunks from 'prismarine-chunk'
|
|
3
|
+
import { Vec3 } from 'vec3'
|
|
4
|
+
import { getCameraCollisionSolidityTable, isStateIdSolidForCameraCollision } from './cameraCollisionSolidity'
|
|
5
|
+
|
|
6
|
+
/** 16³ blocks per section; 1 bit per block. */
|
|
7
|
+
const WORDS_PER_SECTION = 128
|
|
8
|
+
export const CAMERA_COLLISION_BYTES_PER_SECTION = WORDS_PER_SECTION * 4
|
|
9
|
+
|
|
10
|
+
type SectionBitset = Uint32Array
|
|
11
|
+
|
|
12
|
+
const sectionKey = (sx: number, sy: number, sz: number) => `${sx},${sy},${sz}`
|
|
13
|
+
|
|
14
|
+
const alignSection = (coord: number) => Math.floor(coord / 16) * 16
|
|
15
|
+
|
|
16
|
+
const localCoord = (coord: number) => ((coord % 16) + 16) % 16
|
|
17
|
+
|
|
18
|
+
const blockBitIndex = (lx: number, ly: number, lz: number) => lx + lz * 16 + ly * 256
|
|
19
|
+
|
|
20
|
+
const getBit = (bits: SectionBitset, idx: number) => {
|
|
21
|
+
const word = idx >>> 5
|
|
22
|
+
return (bits[word]! & (1 << (idx & 31))) !== 0
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const setBit = (bits: SectionBitset, idx: number, value: boolean) => {
|
|
26
|
+
const word = idx >>> 5
|
|
27
|
+
const mask = 1 << (idx & 31)
|
|
28
|
+
if (value) bits[word]! |= mask
|
|
29
|
+
else bits[word]! &= ~mask
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const isBitsetEmpty = (bits: SectionBitset) => {
|
|
33
|
+
for (let i = 0; i < WORDS_PER_SECTION; i++) {
|
|
34
|
+
if (bits[i]!) return false
|
|
35
|
+
}
|
|
36
|
+
return true
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Sparse 1-bit-per-block solidity cache for third-person voxel DDA.
|
|
41
|
+
* Allocates a 512-byte section bitset only when it contains ≥1 solid block.
|
|
42
|
+
*/
|
|
43
|
+
export class CameraCollisionBlockCache {
|
|
44
|
+
private readonly sections = new Map<string, SectionBitset>()
|
|
45
|
+
private Chunk: ReturnType<typeof Chunks>
|
|
46
|
+
private solidityTable: Uint8Array
|
|
47
|
+
private worldMinY = 0
|
|
48
|
+
private worldMaxY = 256
|
|
49
|
+
|
|
50
|
+
constructor(version: string) {
|
|
51
|
+
this.Chunk = Chunks(version) as ReturnType<typeof Chunks>
|
|
52
|
+
this.solidityTable = getCameraCollisionSolidityTable(version)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
setVersion(version: string) {
|
|
56
|
+
this.Chunk = Chunks(version) as ReturnType<typeof Chunks>
|
|
57
|
+
this.solidityTable = getCameraCollisionSolidityTable(version)
|
|
58
|
+
this.sections.clear()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
setWorldBounds(minY: number, worldHeight: number) {
|
|
62
|
+
if (this.worldMinY === minY && this.worldMaxY === worldHeight) return
|
|
63
|
+
this.worldMinY = minY
|
|
64
|
+
this.worldMaxY = worldHeight
|
|
65
|
+
this.sections.clear()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
clear() {
|
|
69
|
+
this.sections.clear()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Allocated 16³ section bitsets (512 bytes each). */
|
|
73
|
+
getAllocatedSectionCount(): number {
|
|
74
|
+
return this.sections.size
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
getAllocatedBytes(): number {
|
|
78
|
+
return this.sections.size * CAMERA_COLLISION_BYTES_PER_SECTION
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
removeColumn(chunkX: number, chunkZ: number) {
|
|
82
|
+
const syStart = alignSection(this.worldMinY)
|
|
83
|
+
for (let sy = syStart; sy < this.worldMaxY; sy += 16) {
|
|
84
|
+
this.sections.delete(sectionKey(chunkX, sy, chunkZ))
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
ingestColumn(chunkX: number, chunkZ: number, chunkJson: unknown) {
|
|
89
|
+
const chunk = this.Chunk.fromJson(chunkJson as Parameters<ReturnType<typeof Chunks>['fromJson']>[0]) as unknown as {
|
|
90
|
+
getBlockStateId: (pos: Vec3) => number
|
|
91
|
+
}
|
|
92
|
+
const pos = new Vec3(0, 0, 0)
|
|
93
|
+
|
|
94
|
+
for (let y = this.worldMinY; y < this.worldMaxY; y++) {
|
|
95
|
+
pos.y = y
|
|
96
|
+
const sy = alignSection(y)
|
|
97
|
+
const ly = y - sy
|
|
98
|
+
for (let lz = 0; lz < 16; lz++) {
|
|
99
|
+
pos.z = lz
|
|
100
|
+
const wz = chunkZ + lz
|
|
101
|
+
const sz = alignSection(wz)
|
|
102
|
+
const lzLocal = wz - sz
|
|
103
|
+
for (let lx = 0; lx < 16; lx++) {
|
|
104
|
+
pos.x = lx
|
|
105
|
+
const stateId = chunk.getBlockStateId(pos) || 0
|
|
106
|
+
if (!isStateIdSolidForCameraCollision(stateId, this.solidityTable)) continue
|
|
107
|
+
|
|
108
|
+
const wx = chunkX + lx
|
|
109
|
+
const sx = alignSection(wx)
|
|
110
|
+
const key = sectionKey(sx, sy, sz)
|
|
111
|
+
let bits = this.sections.get(key)
|
|
112
|
+
if (!bits) {
|
|
113
|
+
bits = new Uint32Array(WORDS_PER_SECTION)
|
|
114
|
+
this.sections.set(key, bits)
|
|
115
|
+
}
|
|
116
|
+
setBit(bits, blockBitIndex(wx - sx, ly, lzLocal), true)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
setBlockStateId(x: number, y: number, z: number, stateId: number) {
|
|
123
|
+
if (y < this.worldMinY || y >= this.worldMaxY) return
|
|
124
|
+
|
|
125
|
+
const sx = alignSection(x)
|
|
126
|
+
const sy = alignSection(y)
|
|
127
|
+
const sz = alignSection(z)
|
|
128
|
+
const key = sectionKey(sx, sy, sz)
|
|
129
|
+
const idx = blockBitIndex(localCoord(x), y - sy, localCoord(z))
|
|
130
|
+
const solid = isStateIdSolidForCameraCollision(stateId, this.solidityTable)
|
|
131
|
+
|
|
132
|
+
if (solid) {
|
|
133
|
+
let bits = this.sections.get(key)
|
|
134
|
+
if (!bits) {
|
|
135
|
+
bits = new Uint32Array(WORDS_PER_SECTION)
|
|
136
|
+
this.sections.set(key, bits)
|
|
137
|
+
}
|
|
138
|
+
setBit(bits, idx, true)
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const bits = this.sections.get(key)
|
|
143
|
+
if (!bits) return
|
|
144
|
+
setBit(bits, idx, false)
|
|
145
|
+
if (isBitsetEmpty(bits)) this.sections.delete(key)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
isSolidBlock(x: number, y: number, z: number): boolean {
|
|
149
|
+
if (y < this.worldMinY || y >= this.worldMaxY) return false
|
|
150
|
+
|
|
151
|
+
const bits = this.sections.get(sectionKey(alignSection(x), alignSection(y), alignSection(z)))
|
|
152
|
+
if (!bits) return false
|
|
153
|
+
|
|
154
|
+
return getBit(bits, blockBitIndex(localCoord(x), y - alignSection(y), localCoord(z)))
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import MinecraftData from 'minecraft-data'
|
|
3
|
+
import PrismarineBlockLoader from 'prismarine-block'
|
|
4
|
+
import moreBlockDataGeneratedJson from '../lib/moreBlockDataGenerated.json'
|
|
5
|
+
|
|
6
|
+
const solidityCache = new Map<string, Uint8Array>()
|
|
7
|
+
|
|
8
|
+
const PASS_THROUGH_NAMES = new Set(['water', 'flowing_water', 'lava', 'flowing_lava', 'grass', 'short_grass', 'tall_grass'])
|
|
9
|
+
|
|
10
|
+
/** 1 = blocks third-person camera DDA; 0 = pass-through (air, plants, fluids). */
|
|
11
|
+
export function getCameraCollisionSolidityTable(version: string): Uint8Array {
|
|
12
|
+
const cached = solidityCache.get(version)
|
|
13
|
+
if (cached) return cached
|
|
14
|
+
|
|
15
|
+
const mcData = MinecraftData(version)
|
|
16
|
+
const Block = PrismarineBlockLoader(version)
|
|
17
|
+
const noOcclusionsSet = new Set(Object.keys(moreBlockDataGeneratedJson.noOcclusions))
|
|
18
|
+
const invisibleNames = new Set(mcData.blocksArray.filter(b => moreBlockDataGeneratedJson.invisibleBlocks[b.name]).map(b => b.name))
|
|
19
|
+
|
|
20
|
+
let maxStateId = 0
|
|
21
|
+
for (const idStr of Object.keys((mcData as { blocksByStateId: Record<string, unknown> }).blocksByStateId)) {
|
|
22
|
+
maxStateId = Math.max(maxStateId, Number(idStr))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const table = new Uint8Array(maxStateId + 1)
|
|
26
|
+
for (const idStr of Object.keys((mcData as { blocksByStateId: Record<string, unknown> }).blocksByStateId)) {
|
|
27
|
+
const stateId = Number(idStr)
|
|
28
|
+
if (!stateId) continue
|
|
29
|
+
|
|
30
|
+
const block = (
|
|
31
|
+
Block as { fromStateId: (id: number, y: number) => { name: string; transparent: boolean; boundingBox: string; shapes?: number[][] } | null }
|
|
32
|
+
).fromStateId(stateId, 0)
|
|
33
|
+
if (!block) continue
|
|
34
|
+
if (invisibleNames.has(block.name)) continue
|
|
35
|
+
if (noOcclusionsSet.has(block.name)) continue
|
|
36
|
+
if (PASS_THROUGH_NAMES.has(block.name)) continue
|
|
37
|
+
if (block.boundingBox === 'empty') continue
|
|
38
|
+
if (block.transparent && block.boundingBox !== 'block') continue
|
|
39
|
+
if (!block.shapes || block.shapes.length === 0) continue
|
|
40
|
+
|
|
41
|
+
table[stateId] = 1
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
solidityCache.set(version, table)
|
|
45
|
+
return table
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function isStateIdSolidForCameraCollision(stateId: number, solidityTable: Uint8Array): boolean {
|
|
49
|
+
return stateId > 0 && stateId < solidityTable.length && solidityTable[stateId] === 1
|
|
50
|
+
}
|
|
@@ -3,7 +3,7 @@ import * as THREE from 'three'
|
|
|
3
3
|
import * as nbt from 'prismarine-nbt'
|
|
4
4
|
import { Vec3 } from 'vec3'
|
|
5
5
|
import { MesherGeometryOutput } from '../mesher-shared/shared'
|
|
6
|
-
import { getShaderCubeResources
|
|
6
|
+
import { getShaderCubeResources } from '../wasm-mesher/bridge/shaderCubeBridge'
|
|
7
7
|
import {
|
|
8
8
|
createCubeBlockMaterial,
|
|
9
9
|
computeSectionOriginRel,
|
|
@@ -22,19 +22,12 @@ import {
|
|
|
22
22
|
setLegacyLightmapParams,
|
|
23
23
|
type RenderOrigin
|
|
24
24
|
} from './shaders/legacyBlockShader'
|
|
25
|
-
import {
|
|
25
|
+
import { sectionIntersectsFrustum, setupLegacySectionMatrix, updateLegacySectionCullState } from './legacySectionCull'
|
|
26
26
|
import { createShaderCubeMesh, disposeShaderCubeMesh } from './shaderCubeMesh'
|
|
27
27
|
import { GlobalBlockBuffer } from './globalBlockBuffer'
|
|
28
28
|
import { buildVisibleCubeSpans } from './cubeDrawSpans'
|
|
29
29
|
import { GlobalLegacyBuffer, type LegacySectionGeometry } from './globalLegacyBuffer'
|
|
30
|
-
import {
|
|
31
|
-
computeShaderSectionRaycastAabb,
|
|
32
|
-
isPointInsideAabb,
|
|
33
|
-
raycastAabb,
|
|
34
|
-
raycastShaderBlocksAabb,
|
|
35
|
-
sectionAabbIntersectsRay,
|
|
36
|
-
type ShaderSectionRaycastEntry
|
|
37
|
-
} from './sectionRaycastAabb'
|
|
30
|
+
import { computeShaderSectionRaycastAabb, type ShaderSectionRaycastEntry } from './sectionRaycastAabb'
|
|
38
31
|
import { getMesh } from './entity/EntityMesh'
|
|
39
32
|
import type { WorldRendererThree } from './worldRendererThree'
|
|
40
33
|
import { armorModel } from './entity/armorModels'
|
|
@@ -170,11 +163,8 @@ export class ChunkMeshManager {
|
|
|
170
163
|
globalBlockBuffer: GlobalBlockBuffer | null = null
|
|
171
164
|
globalLegacyBuffer: GlobalLegacyBuffer | null = null
|
|
172
165
|
globalLegacyBlendBuffer: GlobalLegacyBuffer | null = null
|
|
173
|
-
/** Tight world AABBs for
|
|
166
|
+
/** Tight world AABBs for shader-cube frustum culling (section centers). */
|
|
174
167
|
private readonly shaderSectionRaycastBoxes = new Map<string, ShaderSectionRaycastEntry>()
|
|
175
|
-
/** Per-raycast block dedup; safe while the eye is inside at most one section aggregate AABB per call. */
|
|
176
|
-
private readonly blockRaycastVisitGen = new Uint16Array(4096)
|
|
177
|
-
private blockRaycastVisitStamp = 1
|
|
178
168
|
|
|
179
169
|
// Performance tracking
|
|
180
170
|
private hits = 0
|
|
@@ -738,35 +728,6 @@ export class ChunkMeshManager {
|
|
|
738
728
|
this.markCullDirty()
|
|
739
729
|
}
|
|
740
730
|
|
|
741
|
-
raycastGlobalLegacySections(raycaster: THREE.Raycaster, origin: THREE.Vector3, maxCenterDistance: number): number | undefined {
|
|
742
|
-
const maxDistSq = maxCenterDistance * maxCenterDistance
|
|
743
|
-
const dirX = raycaster.ray.direction.x
|
|
744
|
-
const dirY = raycaster.ray.direction.y
|
|
745
|
-
const dirZ = raycaster.ray.direction.z
|
|
746
|
-
const far = raycaster.far
|
|
747
|
-
const halfExtent = LEGACY_SECTION_HALF_EXTENT + 0.01
|
|
748
|
-
const candidates: string[] = []
|
|
749
|
-
for (const [key] of this.legacyCullSections) {
|
|
750
|
-
const section = this.sectionObjects[key]
|
|
751
|
-
if (!section || section.worldX === undefined) continue
|
|
752
|
-
const dx = section.worldX - origin.x
|
|
753
|
-
const dy = (section.worldY ?? 0) - origin.y
|
|
754
|
-
const dz = (section.worldZ ?? 0) - origin.z
|
|
755
|
-
if (dx * dx + dy * dy + dz * dz > maxDistSq) continue
|
|
756
|
-
if (!sectionAabbIntersectsRay(section.worldX, section.worldY ?? 0, section.worldZ ?? 0, origin.x, origin.y, origin.z, dirX, dirY, dirZ, far, halfExtent))
|
|
757
|
-
continue
|
|
758
|
-
candidates.push(key)
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
if (candidates.length === 0) return undefined
|
|
762
|
-
|
|
763
|
-
const hits: THREE.Intersection[] = []
|
|
764
|
-
this.globalLegacyBuffer?.raycastSections(raycaster, candidates, hits)
|
|
765
|
-
this.globalLegacyBlendBuffer?.raycastSections(raycaster, candidates, hits)
|
|
766
|
-
|
|
767
|
-
return hits[0]?.distance
|
|
768
|
-
}
|
|
769
|
-
|
|
770
731
|
registerShaderSectionRaycastBox(
|
|
771
732
|
sectionKey: string,
|
|
772
733
|
words: Uint32Array,
|
|
@@ -792,91 +753,6 @@ export class ChunkMeshManager {
|
|
|
792
753
|
this.shaderSectionRaycastBoxes.delete(sectionKey)
|
|
793
754
|
}
|
|
794
755
|
|
|
795
|
-
/** Closest hit against registered shader-cube AABBs (world-space ray). */
|
|
796
|
-
raycastShaderSectionAABBs(originWorld: THREE.Vector3, direction: THREE.Vector3, maxDist: number, maxCenterDistance = 80): number | undefined {
|
|
797
|
-
const ox = originWorld.x
|
|
798
|
-
const oy = originWorld.y
|
|
799
|
-
const oz = originWorld.z
|
|
800
|
-
const dx = direction.x
|
|
801
|
-
const dy = direction.y
|
|
802
|
-
const dz = direction.z
|
|
803
|
-
const maxCenterDistSq = maxCenterDistance * maxCenterDistance
|
|
804
|
-
|
|
805
|
-
let closest = maxDist
|
|
806
|
-
let found = false
|
|
807
|
-
|
|
808
|
-
this.blockRaycastVisitStamp++
|
|
809
|
-
if (this.blockRaycastVisitStamp >= 65535) {
|
|
810
|
-
this.blockRaycastVisitGen.fill(0)
|
|
811
|
-
this.blockRaycastVisitStamp = 1
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
for (const [key, entry] of this.shaderSectionRaycastBoxes) {
|
|
815
|
-
const section = this.sectionObjects[key]
|
|
816
|
-
if (section && !section.visible) continue
|
|
817
|
-
|
|
818
|
-
const { box } = entry
|
|
819
|
-
const dcx = box.cx - ox
|
|
820
|
-
const dcy = box.cy - oy
|
|
821
|
-
const dcz = box.cz - oz
|
|
822
|
-
if (dcx * dcx + dcy * dcy + dcz * dcz > maxCenterDistSq) continue
|
|
823
|
-
|
|
824
|
-
let t = raycastAabb(ox, oy, oz, dx, dy, dz, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, closest)
|
|
825
|
-
if (t === undefined && isPointInsideAabb(ox, oy, oz, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ)) {
|
|
826
|
-
const gb = this.globalBlockBuffer
|
|
827
|
-
const slot = gb?.getSectionSlot(key)
|
|
828
|
-
if (gb && slot) {
|
|
829
|
-
t = raycastShaderBlocksAabb(
|
|
830
|
-
gb.getW0(),
|
|
831
|
-
slot.start,
|
|
832
|
-
slot.count,
|
|
833
|
-
1,
|
|
834
|
-
entry.sectionCenterX,
|
|
835
|
-
entry.sectionCenterY,
|
|
836
|
-
entry.sectionCenterZ,
|
|
837
|
-
ox,
|
|
838
|
-
oy,
|
|
839
|
-
oz,
|
|
840
|
-
dx,
|
|
841
|
-
dy,
|
|
842
|
-
dz,
|
|
843
|
-
closest,
|
|
844
|
-
this.blockRaycastVisitGen,
|
|
845
|
-
this.blockRaycastVisitStamp
|
|
846
|
-
)
|
|
847
|
-
} else {
|
|
848
|
-
const def = this.sectionObjects[key]?.deferredShaderCubes
|
|
849
|
-
if (def) {
|
|
850
|
-
t = raycastShaderBlocksAabb(
|
|
851
|
-
def.words,
|
|
852
|
-
0,
|
|
853
|
-
def.count,
|
|
854
|
-
SHADER_CUBES_WORDS_PER_FACE,
|
|
855
|
-
entry.sectionCenterX,
|
|
856
|
-
entry.sectionCenterY,
|
|
857
|
-
entry.sectionCenterZ,
|
|
858
|
-
ox,
|
|
859
|
-
oy,
|
|
860
|
-
oz,
|
|
861
|
-
dx,
|
|
862
|
-
dy,
|
|
863
|
-
dz,
|
|
864
|
-
closest,
|
|
865
|
-
this.blockRaycastVisitGen,
|
|
866
|
-
this.blockRaycastVisitStamp
|
|
867
|
-
)
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
if (t !== undefined && t < closest) {
|
|
872
|
-
closest = t
|
|
873
|
-
found = true
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
return found ? closest : undefined
|
|
878
|
-
}
|
|
879
|
-
|
|
880
756
|
/**
|
|
881
757
|
* Update or create a section with new geometry data
|
|
882
758
|
*/
|
|
@@ -644,46 +644,6 @@ export class GlobalLegacyBuffer {
|
|
|
644
644
|
if (uf?.value?.set) uf.value.set(cameraOriginFrac.x, cameraOriginFrac.y, cameraOriginFrac.z)
|
|
645
645
|
}
|
|
646
646
|
|
|
647
|
-
raycastSections(raycaster: THREE.Raycaster, sectionKeys: Iterable<string>, out: THREE.Intersection[]): THREE.Intersection[] {
|
|
648
|
-
const ray = raycaster.ray
|
|
649
|
-
const closest = raycaster.near
|
|
650
|
-
const far = raycaster.far
|
|
651
|
-
_raycastOrigin.copy(ray.origin).sub(_raycastRenderOrigin.set(this.renderOrigin.x, this.renderOrigin.y, this.renderOrigin.z))
|
|
652
|
-
_raycastRay.origin.copy(_raycastOrigin)
|
|
653
|
-
_raycastRay.direction.copy(ray.direction)
|
|
654
|
-
|
|
655
|
-
for (const key of sectionKeys) {
|
|
656
|
-
const slot = this.sectionSlots.get(key)
|
|
657
|
-
if (!slot) continue
|
|
658
|
-
|
|
659
|
-
const dstVertBase = slot.start * VERTS_PER_QUAD
|
|
660
|
-
const dstFloatBase = dstVertBase * FLOATS_PER_VERT
|
|
661
|
-
const dstIndexBase = slot.start * INDICES_PER_QUAD
|
|
662
|
-
const indexLen = slot.count * INDICES_PER_QUAD
|
|
663
|
-
|
|
664
|
-
for (let i = 0; i < indexLen; i += 3) {
|
|
665
|
-
const i0 = this.indices[dstIndexBase + i]!
|
|
666
|
-
const i1 = this.indices[dstIndexBase + i + 1]!
|
|
667
|
-
const i2 = this.indices[dstIndexBase + i + 2]!
|
|
668
|
-
if (i0 === i1 && i1 === i2) continue
|
|
669
|
-
|
|
670
|
-
const hit = intersectTriangle(_raycastRay, this.positions, this.aOrigin, dstFloatBase, i0, i1, i2, closest, far)
|
|
671
|
-
if (hit !== null) {
|
|
672
|
-
out.push({
|
|
673
|
-
distance: hit,
|
|
674
|
-
point: ray.at(hit, new THREE.Vector3()),
|
|
675
|
-
object: this.mesh,
|
|
676
|
-
face: null,
|
|
677
|
-
faceIndex: Math.floor(i / 3)
|
|
678
|
-
})
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
out.sort((a, b) => a.distance - b.distance)
|
|
684
|
-
return out
|
|
685
|
-
}
|
|
686
|
-
|
|
687
647
|
getHighWatermark(): number {
|
|
688
648
|
return this.highWatermark
|
|
689
649
|
}
|
|
@@ -980,70 +940,3 @@ export class GlobalLegacyBuffer {
|
|
|
980
940
|
this.pendingRanges.length = 0
|
|
981
941
|
}
|
|
982
942
|
}
|
|
983
|
-
|
|
984
|
-
const _vA = new THREE.Vector3()
|
|
985
|
-
const _vB = new THREE.Vector3()
|
|
986
|
-
const _vC = new THREE.Vector3()
|
|
987
|
-
const _edge1 = new THREE.Vector3()
|
|
988
|
-
const _edge2 = new THREE.Vector3()
|
|
989
|
-
const _normal = new THREE.Vector3()
|
|
990
|
-
const _raycastOrigin = new THREE.Vector3()
|
|
991
|
-
const _raycastRenderOrigin = new THREE.Vector3()
|
|
992
|
-
const _raycastRay = new THREE.Ray()
|
|
993
|
-
|
|
994
|
-
function readWorldVertex(positions: Float32Array, aOrigin: Float32Array, floatBase: number, vertIndex: number, target: THREE.Vector3): void {
|
|
995
|
-
const f = floatBase + vertIndex * FLOATS_PER_VERT
|
|
996
|
-
target.set(aOrigin[f]! + positions[f]!, aOrigin[f + 1]! + positions[f + 1]!, aOrigin[f + 2]! + positions[f + 2]!)
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
function intersectTriangle(
|
|
1000
|
-
ray: THREE.Ray,
|
|
1001
|
-
positions: Float32Array,
|
|
1002
|
-
aOrigin: Float32Array,
|
|
1003
|
-
floatBase: number,
|
|
1004
|
-
i0: number,
|
|
1005
|
-
i1: number,
|
|
1006
|
-
i2: number,
|
|
1007
|
-
near: number,
|
|
1008
|
-
far: number
|
|
1009
|
-
): number | null {
|
|
1010
|
-
readWorldVertex(positions, aOrigin, floatBase, i0, _vA)
|
|
1011
|
-
readWorldVertex(positions, aOrigin, floatBase, i1, _vB)
|
|
1012
|
-
readWorldVertex(positions, aOrigin, floatBase, i2, _vC)
|
|
1013
|
-
|
|
1014
|
-
_edge1.subVectors(_vB, _vA)
|
|
1015
|
-
_edge2.subVectors(_vC, _vA)
|
|
1016
|
-
_normal.crossVectors(_edge1, _edge2)
|
|
1017
|
-
|
|
1018
|
-
const denom = _normal.dot(ray.direction)
|
|
1019
|
-
if (Math.abs(denom) < 1e-8) return null
|
|
1020
|
-
|
|
1021
|
-
const t = _vA.clone().sub(ray.origin).dot(_normal) / denom
|
|
1022
|
-
if (t < near || t > far) return null
|
|
1023
|
-
|
|
1024
|
-
const p = ray.at(t, new THREE.Vector3())
|
|
1025
|
-
if (!pointInTriangle(p, _vA, _vB, _vC)) return null
|
|
1026
|
-
return t
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
function pointInTriangle(p: THREE.Vector3, a: THREE.Vector3, b: THREE.Vector3, c: THREE.Vector3): boolean {
|
|
1030
|
-
_edge1.subVectors(b, a)
|
|
1031
|
-
_edge2.subVectors(c, a)
|
|
1032
|
-
const n = _normal.crossVectors(_edge1, _edge2).normalize()
|
|
1033
|
-
|
|
1034
|
-
const ab = _edge1
|
|
1035
|
-
const ac = _edge2
|
|
1036
|
-
const ap = p.clone().sub(a)
|
|
1037
|
-
|
|
1038
|
-
const d00 = ab.dot(ab)
|
|
1039
|
-
const d01 = ab.dot(ac)
|
|
1040
|
-
const d11 = ac.dot(ac)
|
|
1041
|
-
const d20 = ap.dot(ab)
|
|
1042
|
-
const d21 = ap.dot(ac)
|
|
1043
|
-
const denom = d00 * d11 - d01 * d01
|
|
1044
|
-
if (Math.abs(denom) < 1e-12) return false
|
|
1045
|
-
const v = (d11 * d20 - d01 * d21) / denom
|
|
1046
|
-
const w = (d00 * d21 - d01 * d20) / denom
|
|
1047
|
-
const u = 1 - v - w
|
|
1048
|
-
return u >= -1e-4 && v >= -1e-4 && w >= -1e-4
|
|
1049
|
-
}
|