minecraft-renderer 0.1.58 → 0.1.59
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/minecraft-renderer.js +58 -58
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +420 -420
- package/package.json +1 -1
- package/src/three/chunkMeshManager.ts +60 -7
- package/src/three/globalBlockBuffer.ts +9 -0
- package/src/three/sectionRaycastAabb.ts +101 -1
- package/src/wasm-mesher/tests/sectionRaycastAabb.test.ts +57 -0
package/package.json
CHANGED
|
@@ -4,14 +4,16 @@ import * as THREE from 'three'
|
|
|
4
4
|
import * as nbt from 'prismarine-nbt'
|
|
5
5
|
import { Vec3 } from 'vec3'
|
|
6
6
|
import { MesherGeometryOutput } from '../mesher-shared/shared'
|
|
7
|
-
import { getShaderCubeResources } from '../wasm-mesher/bridge/shaderCubeBridge'
|
|
7
|
+
import { getShaderCubeResources, SHADER_CUBES_WORDS_PER_FACE } from '../wasm-mesher/bridge/shaderCubeBridge'
|
|
8
8
|
import { createCubeBlockMaterial } from './shaders/cubeBlockShader'
|
|
9
9
|
import { createShaderCubeMesh, disposeShaderCubeMesh } from './shaderCubeMesh'
|
|
10
10
|
import { GlobalBlockBuffer } from './globalBlockBuffer'
|
|
11
11
|
import {
|
|
12
12
|
computeShaderSectionRaycastAabb,
|
|
13
|
+
isPointInsideAabb,
|
|
13
14
|
raycastAabb,
|
|
14
|
-
|
|
15
|
+
raycastShaderBlocksAabb,
|
|
16
|
+
type ShaderSectionRaycastEntry,
|
|
15
17
|
} from './sectionRaycastAabb'
|
|
16
18
|
import { chunkPos } from '../lib/simpleUtils'
|
|
17
19
|
import { renderSign } from '../sign-renderer'
|
|
@@ -99,8 +101,11 @@ export class ChunkMeshManager {
|
|
|
99
101
|
private cubeShaderMaterial: THREE.ShaderMaterial | null = null
|
|
100
102
|
/** One instanced mesh for all shader-cube faces (single draw call). */
|
|
101
103
|
globalBlockBuffer: GlobalBlockBuffer | null = null
|
|
102
|
-
/** Tight world AABBs for third-person raycast;
|
|
103
|
-
private readonly shaderSectionRaycastBoxes = new Map<string,
|
|
104
|
+
/** Tight world AABBs for third-person raycast; block word0 read from GlobalBlockBuffer or deferred. */
|
|
105
|
+
private readonly shaderSectionRaycastBoxes = new Map<string, ShaderSectionRaycastEntry>()
|
|
106
|
+
/** Per-raycast block dedup; safe while the eye is inside at most one section aggregate AABB per call. */
|
|
107
|
+
private readonly blockRaycastVisitGen = new Uint16Array(4096)
|
|
108
|
+
private blockRaycastVisitStamp = 1
|
|
104
109
|
|
|
105
110
|
// Performance tracking
|
|
106
111
|
private hits = 0
|
|
@@ -256,7 +261,12 @@ export class ChunkMeshManager {
|
|
|
256
261
|
): void {
|
|
257
262
|
const box = computeShaderSectionRaycastAabb(words, faceCount, sectionCenterX, sectionCenterY, sectionCenterZ)
|
|
258
263
|
if (box) {
|
|
259
|
-
this.shaderSectionRaycastBoxes.set(sectionKey,
|
|
264
|
+
this.shaderSectionRaycastBoxes.set(sectionKey, {
|
|
265
|
+
box,
|
|
266
|
+
sectionCenterX,
|
|
267
|
+
sectionCenterY,
|
|
268
|
+
sectionCenterZ,
|
|
269
|
+
})
|
|
260
270
|
} else {
|
|
261
271
|
this.shaderSectionRaycastBoxes.delete(sectionKey)
|
|
262
272
|
}
|
|
@@ -284,20 +294,63 @@ export class ChunkMeshManager {
|
|
|
284
294
|
let closest = maxDist
|
|
285
295
|
let found = false
|
|
286
296
|
|
|
287
|
-
|
|
297
|
+
this.blockRaycastVisitStamp++
|
|
298
|
+
if (this.blockRaycastVisitStamp >= 65535) {
|
|
299
|
+
this.blockRaycastVisitGen.fill(0)
|
|
300
|
+
this.blockRaycastVisitStamp = 1
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
for (const [key, entry] of this.shaderSectionRaycastBoxes) {
|
|
288
304
|
const section = this.sectionObjects[key]
|
|
289
305
|
if (section && !section.visible) continue
|
|
290
306
|
|
|
307
|
+
const { box } = entry
|
|
291
308
|
const dcx = box.cx - ox
|
|
292
309
|
const dcy = box.cy - oy
|
|
293
310
|
const dcz = box.cz - oz
|
|
294
311
|
if (dcx * dcx + dcy * dcy + dcz * dcz > maxCenterDistSq) continue
|
|
295
312
|
|
|
296
|
-
|
|
313
|
+
let t = raycastAabb(
|
|
297
314
|
ox, oy, oz, dx, dy, dz,
|
|
298
315
|
box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ,
|
|
299
316
|
closest,
|
|
300
317
|
)
|
|
318
|
+
if (t === undefined && isPointInsideAabb(ox, oy, oz, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ)) {
|
|
319
|
+
const gb = this.globalBlockBuffer
|
|
320
|
+
const slot = gb?.getSectionSlot(key)
|
|
321
|
+
if (gb && slot) {
|
|
322
|
+
t = raycastShaderBlocksAabb(
|
|
323
|
+
gb.getW0(),
|
|
324
|
+
slot.start,
|
|
325
|
+
slot.count,
|
|
326
|
+
1,
|
|
327
|
+
entry.sectionCenterX,
|
|
328
|
+
entry.sectionCenterY,
|
|
329
|
+
entry.sectionCenterZ,
|
|
330
|
+
ox, oy, oz, dx, dy, dz,
|
|
331
|
+
closest,
|
|
332
|
+
this.blockRaycastVisitGen,
|
|
333
|
+
this.blockRaycastVisitStamp,
|
|
334
|
+
)
|
|
335
|
+
} else {
|
|
336
|
+
const def = this.sectionObjects[key]?.deferredShaderCubes
|
|
337
|
+
if (def) {
|
|
338
|
+
t = raycastShaderBlocksAabb(
|
|
339
|
+
def.words,
|
|
340
|
+
0,
|
|
341
|
+
def.count,
|
|
342
|
+
SHADER_CUBES_WORDS_PER_FACE,
|
|
343
|
+
entry.sectionCenterX,
|
|
344
|
+
entry.sectionCenterY,
|
|
345
|
+
entry.sectionCenterZ,
|
|
346
|
+
ox, oy, oz, dx, dy, dz,
|
|
347
|
+
closest,
|
|
348
|
+
this.blockRaycastVisitGen,
|
|
349
|
+
this.blockRaycastVisitStamp,
|
|
350
|
+
)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
301
354
|
if (t !== undefined && t < closest) {
|
|
302
355
|
closest = t
|
|
303
356
|
found = true
|
|
@@ -106,6 +106,15 @@ export class GlobalBlockBuffer {
|
|
|
106
106
|
return this.sectionSlots.has(sectionKey)
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
getSectionSlot (sectionKey: string): { start: number, count: number } | undefined {
|
|
110
|
+
return this.sectionSlots.get(sectionKey)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Fetch fresh each raycast — growCapacity reallocates the backing array. */
|
|
114
|
+
getW0 (): Uint32Array {
|
|
115
|
+
return this.w0
|
|
116
|
+
}
|
|
117
|
+
|
|
109
118
|
/** Copy live GPU words and remove the section (sci-fi reveal hide / restore). */
|
|
110
119
|
takeSectionData (sectionKey: string): GlobalBlockBufferShaderData | undefined {
|
|
111
120
|
const slot = this.sectionSlots.get(sectionKey)
|
|
@@ -14,6 +14,13 @@ export type ShaderSectionRaycastBox = {
|
|
|
14
14
|
cz: number
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export type ShaderSectionRaycastEntry = {
|
|
18
|
+
box: ShaderSectionRaycastBox
|
|
19
|
+
sectionCenterX: number
|
|
20
|
+
sectionCenterY: number
|
|
21
|
+
sectionCenterZ: number
|
|
22
|
+
}
|
|
23
|
+
|
|
17
24
|
/**
|
|
18
25
|
* Tight world-space AABB covering occupied shader-cube blocks in a section.
|
|
19
26
|
* `sectionCenter*` is geometryData.sx/sy/sz (section base + 8).
|
|
@@ -74,7 +81,7 @@ export function computeShaderSectionRaycastAabb (
|
|
|
74
81
|
}
|
|
75
82
|
}
|
|
76
83
|
|
|
77
|
-
function isPointInsideAabb (
|
|
84
|
+
export function isPointInsideAabb (
|
|
78
85
|
ox: number,
|
|
79
86
|
oy: number,
|
|
80
87
|
oz: number,
|
|
@@ -150,6 +157,99 @@ export function raycastAabb (
|
|
|
150
157
|
return tmin <= tmax && tmin >= 0 ? tmin : undefined
|
|
151
158
|
}
|
|
152
159
|
|
|
160
|
+
/** Ray origin inside AABB: distance to exit face along the ray. */
|
|
161
|
+
export function raycastAabbFromInside (
|
|
162
|
+
ox: number,
|
|
163
|
+
oy: number,
|
|
164
|
+
oz: number,
|
|
165
|
+
dx: number,
|
|
166
|
+
dy: number,
|
|
167
|
+
dz: number,
|
|
168
|
+
minX: number,
|
|
169
|
+
minY: number,
|
|
170
|
+
minZ: number,
|
|
171
|
+
maxX: number,
|
|
172
|
+
maxY: number,
|
|
173
|
+
maxZ: number,
|
|
174
|
+
maxDist: number,
|
|
175
|
+
): number | undefined {
|
|
176
|
+
let tExit = maxDist
|
|
177
|
+
|
|
178
|
+
if (Math.abs(dx) >= 1e-8) {
|
|
179
|
+
const t = dx > 0 ? (maxX - ox) / dx : (minX - ox) / dx
|
|
180
|
+
if (t > 1e-6) tExit = Math.min(tExit, t)
|
|
181
|
+
} else if (ox < minX || ox > maxX) return undefined
|
|
182
|
+
|
|
183
|
+
if (Math.abs(dy) >= 1e-8) {
|
|
184
|
+
const t = dy > 0 ? (maxY - oy) / dy : (minY - oy) / dy
|
|
185
|
+
if (t > 1e-6) tExit = Math.min(tExit, t)
|
|
186
|
+
} else if (oy < minY || oy > maxY) return undefined
|
|
187
|
+
|
|
188
|
+
if (Math.abs(dz) >= 1e-8) {
|
|
189
|
+
const t = dz > 0 ? (maxZ - oz) / dz : (minZ - oz) / dz
|
|
190
|
+
if (t > 1e-6) tExit = Math.min(tExit, t)
|
|
191
|
+
} else if (oz < minZ || oz > maxZ) return undefined
|
|
192
|
+
|
|
193
|
+
return tExit <= maxDist ? tExit : undefined
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Per-block raycast; `word0Stride` 1 = GlobalBlockBuffer SoA, 4 = deferred AoS. */
|
|
197
|
+
export function raycastShaderBlocksAabb (
|
|
198
|
+
w0Source: Uint32Array,
|
|
199
|
+
start: number,
|
|
200
|
+
faceCount: number,
|
|
201
|
+
word0Stride: number,
|
|
202
|
+
sectionCenterX: number,
|
|
203
|
+
sectionCenterY: number,
|
|
204
|
+
sectionCenterZ: number,
|
|
205
|
+
ox: number,
|
|
206
|
+
oy: number,
|
|
207
|
+
oz: number,
|
|
208
|
+
dx: number,
|
|
209
|
+
dy: number,
|
|
210
|
+
dz: number,
|
|
211
|
+
maxDist: number,
|
|
212
|
+
visitGen: Uint16Array,
|
|
213
|
+
visitStamp: number,
|
|
214
|
+
): number | undefined {
|
|
215
|
+
const baseX = sectionCenterX - 8
|
|
216
|
+
const baseY = sectionCenterY - 8
|
|
217
|
+
const baseZ = sectionCenterZ - 8
|
|
218
|
+
|
|
219
|
+
let closest = maxDist
|
|
220
|
+
let found = false
|
|
221
|
+
|
|
222
|
+
for (let i = 0; i < faceCount; i++) {
|
|
223
|
+
const w0 = w0Source[start + i * word0Stride]!
|
|
224
|
+
const lx = w0 & ((1 << WORD0.LX_BITS) - 1)
|
|
225
|
+
const ly = (w0 >> WORD0.LY_SHIFT) & ((1 << WORD0.LY_BITS) - 1)
|
|
226
|
+
const lz = (w0 >> WORD0.LZ_SHIFT) & ((1 << WORD0.LZ_BITS) - 1)
|
|
227
|
+
const visitIdx = lx + (ly << 4) + (lz << 8)
|
|
228
|
+
if (visitGen[visitIdx] === visitStamp) continue
|
|
229
|
+
visitGen[visitIdx] = visitStamp
|
|
230
|
+
|
|
231
|
+
const minX = baseX + lx
|
|
232
|
+
const minY = baseY + ly
|
|
233
|
+
const minZ = baseZ + lz
|
|
234
|
+
const maxX = minX + 1
|
|
235
|
+
const maxY = minY + 1
|
|
236
|
+
const maxZ = minZ + 1
|
|
237
|
+
|
|
238
|
+
let t: number | undefined
|
|
239
|
+
if (isPointInsideAabb(ox, oy, oz, minX, minY, minZ, maxX, maxY, maxZ)) {
|
|
240
|
+
t = raycastAabbFromInside(ox, oy, oz, dx, dy, dz, minX, minY, minZ, maxX, maxY, maxZ, closest)
|
|
241
|
+
} else {
|
|
242
|
+
t = raycastAabb(ox, oy, oz, dx, dy, dz, minX, minY, minZ, maxX, maxY, maxZ, closest)
|
|
243
|
+
}
|
|
244
|
+
if (t !== undefined && t < closest) {
|
|
245
|
+
closest = t
|
|
246
|
+
found = true
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return found ? closest : undefined
|
|
251
|
+
}
|
|
252
|
+
|
|
153
253
|
/** 16³ section box centered at (cx, cy, cz) — tests / legacy helper. */
|
|
154
254
|
export function raycastSectionAabb (
|
|
155
255
|
ox: number,
|
|
@@ -3,6 +3,7 @@ import { test, expect } from 'vitest'
|
|
|
3
3
|
import {
|
|
4
4
|
computeShaderSectionRaycastAabb,
|
|
5
5
|
raycastAabb,
|
|
6
|
+
raycastShaderBlocksAabb,
|
|
6
7
|
raycastSectionAabb,
|
|
7
8
|
} from '../../three/sectionRaycastAabb'
|
|
8
9
|
import { SHADER_CUBES_WORDS_PER_FACE } from '../bridge/shaderCubeBridge'
|
|
@@ -46,6 +47,62 @@ test('raycastAabb: origin inside box is ignored', () => {
|
|
|
46
47
|
expect(t).toBeUndefined()
|
|
47
48
|
})
|
|
48
49
|
|
|
50
|
+
test('raycastShaderBlocksAabb: hits wall block when origin is inside section aggregate AABB', () => {
|
|
51
|
+
const words = new Uint32Array(SHADER_CUBES_WORDS_PER_FACE * 3)
|
|
52
|
+
words[0] = 8 | (4 << WORD0.LY_SHIFT) | (8 << WORD0.LZ_SHIFT)
|
|
53
|
+
words[4] = 8 | (5 << WORD0.LY_SHIFT) | (8 << WORD0.LZ_SHIFT)
|
|
54
|
+
words[8] = 8 | (6 << WORD0.LY_SHIFT) | (8 << WORD0.LZ_SHIFT)
|
|
55
|
+
const visitGen = new Uint16Array(4096)
|
|
56
|
+
const visitStamp = 1
|
|
57
|
+
const t = raycastShaderBlocksAabb(words, 0, 3, SHADER_CUBES_WORDS_PER_FACE, 8, 8, 8, 4.5, 5.5, 8.5, 1, 0, 0, 10, visitGen, visitStamp)!
|
|
58
|
+
expect(t).toBeGreaterThan(0)
|
|
59
|
+
expect(t).toBeLessThan(4)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('raycastShaderBlocksAabb: ray into empty space does not hit', () => {
|
|
63
|
+
const words = new Uint32Array(SHADER_CUBES_WORDS_PER_FACE)
|
|
64
|
+
words[0] = 8 | (4 << WORD0.LY_SHIFT) | (8 << WORD0.LZ_SHIFT)
|
|
65
|
+
const visitGen = new Uint16Array(4096)
|
|
66
|
+
const visitStamp = 1
|
|
67
|
+
const t = raycastShaderBlocksAabb(words, 0, 1, SHADER_CUBES_WORDS_PER_FACE, 8, 8, 8, 4.5, 5.5, 8.5, -1, 0, 0, 10, visitGen, visitStamp)
|
|
68
|
+
expect(t).toBeUndefined()
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('raycastShaderBlocksAabb: eye inside solid block uses exit distance', () => {
|
|
72
|
+
const lx = 5
|
|
73
|
+
const ly = 3
|
|
74
|
+
const lz = 7
|
|
75
|
+
const sectionCenterX = 8
|
|
76
|
+
const sectionCenterY = 8
|
|
77
|
+
const sectionCenterZ = 8
|
|
78
|
+
const words = new Uint32Array(SHADER_CUBES_WORDS_PER_FACE)
|
|
79
|
+
words[0] = lx | (ly << WORD0.LY_SHIFT) | (lz << WORD0.LZ_SHIFT)
|
|
80
|
+
const visitGen = new Uint16Array(4096)
|
|
81
|
+
const visitStamp = 1
|
|
82
|
+
const ox = sectionCenterX - 8 + lx + 0.5
|
|
83
|
+
const oy = sectionCenterY - 8 + ly + 0.5
|
|
84
|
+
const oz = sectionCenterZ - 8 + lz + 0.5
|
|
85
|
+
const t = raycastShaderBlocksAabb(
|
|
86
|
+
words, 0, 1, SHADER_CUBES_WORDS_PER_FACE,
|
|
87
|
+
sectionCenterX, sectionCenterY, sectionCenterZ,
|
|
88
|
+
ox, oy, oz, 0, 0, 1, 10, visitGen, visitStamp,
|
|
89
|
+
)!
|
|
90
|
+
expect(t).toBeGreaterThan(0)
|
|
91
|
+
expect(t).toBeLessThanOrEqual(10)
|
|
92
|
+
expect(t).toBeCloseTo(0.5, 5)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test('raycastShaderBlocksAabb: SoA stride-1 layout (GlobalBlockBuffer style)', () => {
|
|
96
|
+
const w0 = new Uint32Array(2)
|
|
97
|
+
w0[0] = 8 | (4 << WORD0.LY_SHIFT) | (8 << WORD0.LZ_SHIFT)
|
|
98
|
+
w0[1] = 8 | (5 << WORD0.LY_SHIFT) | (8 << WORD0.LZ_SHIFT)
|
|
99
|
+
const visitGen = new Uint16Array(4096)
|
|
100
|
+
const visitStamp = 1
|
|
101
|
+
const t = raycastShaderBlocksAabb(w0, 0, 2, 1, 8, 8, 8, 4.5, 5.5, 8.5, 1, 0, 0, 10, visitGen, visitStamp)!
|
|
102
|
+
expect(t).toBeGreaterThan(0)
|
|
103
|
+
expect(t).toBeLessThan(4)
|
|
104
|
+
})
|
|
105
|
+
|
|
49
106
|
test('raycastAabb: narrow floor slab blocks downward ray', () => {
|
|
50
107
|
const words = new Uint32Array(SHADER_CUBES_WORDS_PER_FACE * 2)
|
|
51
108
|
words[0] = 4 | (0 << WORD0.LY_SHIFT) | (4 << WORD0.LZ_SHIFT)
|