minecraft-renderer 0.1.47 → 0.1.49
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 +1 -1
- package/dist/mesher.js.map +2 -2
- package/dist/mesherWasm.js +3740 -183
- package/dist/minecraft-renderer.js +332 -60
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +705 -433
- package/package.json +1 -1
- package/src/graphicsBackend/config.ts +4 -0
- package/src/graphicsBackend/playerState.ts +1 -0
- package/src/graphicsBackend/rendererOptionsSync.ts +1 -0
- package/src/lib/worldrendererCommon.ts +13 -0
- package/src/mesher-shared/exportedGeometryTypes.ts +5 -1
- package/src/mesher-shared/shared.ts +8 -0
- package/src/playerState/playerState.ts +2 -0
- package/src/three/chunkMeshManager.ts +312 -39
- package/src/three/globalBlockBuffer.ts +292 -0
- package/src/three/menuBackground/config.ts +1 -1
- package/src/three/menuBackground/defaultOptions.ts +27 -19
- package/src/three/modules/sciFiWorldReveal.ts +162 -68
- package/src/three/modules/starfield.ts +9 -1
- package/src/three/sectionRaycastAabb.ts +167 -0
- package/src/three/shaderCubeMesh.ts +93 -0
- package/src/three/shaders/cubeBlockShader.ts +354 -0
- package/src/three/shaders/textureIndexMapping.ts +122 -0
- package/src/three/shaders/tintPalette.ts +198 -0
- package/src/three/worldGeometryExport.ts +53 -25
- package/src/three/worldRendererThree.ts +93 -26
- package/src/wasm-mesher/bridge/render-from-wasm.ts +62 -185
- package/src/wasm-mesher/bridge/shaderCubeBridge.ts +396 -0
- package/src/wasm-mesher/runtime-build/wasm_mesher_bg.wasm +0 -0
- package/src/wasm-mesher/tests/sectionRaycastAabb.test.ts +58 -0
- package/src/wasm-mesher/tests/shaderCubeInstances.test.ts +360 -0
- package/src/wasm-mesher/tests/splitColumnWasmOutput.test.ts +11 -4
- package/src/wasm-mesher/worker/mesherWasm.ts +17 -2
|
@@ -3,6 +3,7 @@ import * as THREE from 'three'
|
|
|
3
3
|
import type { WorldRendererThree } from '../worldRendererThree'
|
|
4
4
|
import type { RendererModuleController, RendererModuleManifest } from '../rendererModuleSystem'
|
|
5
5
|
import type { MesherGeometryOutput } from '../../mesher-shared/shared'
|
|
6
|
+
import type { GlobalBlockBufferShaderData } from '../globalBlockBuffer'
|
|
6
7
|
|
|
7
8
|
const SCI_FI_CYAN = new THREE.Color(13 / 255, 234 / 255, 238 / 255)
|
|
8
9
|
const CHUNKS_THRESHOLD = 9
|
|
@@ -21,7 +22,10 @@ interface RevealingSection {
|
|
|
21
22
|
wireframeGroup: THREE.Group
|
|
22
23
|
revealStartTime: number
|
|
23
24
|
phase: 'wireframe' | 'transitioning' | 'complete'
|
|
24
|
-
|
|
25
|
+
/** Legacy + shader render meshes hidden during reveal */
|
|
26
|
+
renderMeshRefs: THREE.Mesh[]
|
|
27
|
+
/** Shader cubes temporarily removed from globalBlockBuffer during reveal. */
|
|
28
|
+
globalShaderRestore?: GlobalBlockBufferShaderData
|
|
25
29
|
wireframeMs: number
|
|
26
30
|
revealMs: number
|
|
27
31
|
}
|
|
@@ -44,6 +48,8 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
44
48
|
private onWorldSwitchedCb: (() => void) | null = null
|
|
45
49
|
private patched = false
|
|
46
50
|
private initialWaveDone = false
|
|
51
|
+
/** True after every section from the first reveal wave has finished animating. */
|
|
52
|
+
private initialRevealWaveSettled = false
|
|
47
53
|
|
|
48
54
|
// Wireframe materials
|
|
49
55
|
private readonly wireframeMaterial!: THREE.LineBasicMaterial
|
|
@@ -61,11 +67,7 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
61
67
|
private originalSceneAdd: ((...object: THREE.Object3D[]) => THREE.Scene) | null = null
|
|
62
68
|
private originalHandleWorkerMessage: ((data: { geometry: MesherGeometryOutput; key: string; type: string }) => void) | null = null
|
|
63
69
|
|
|
64
|
-
private configEnabled = true
|
|
65
|
-
|
|
66
70
|
constructor(private readonly worldRenderer: WorldRendererThree) {
|
|
67
|
-
this.configEnabled = this.worldRenderer.worldRendererConfig.futuristicReveal === true
|
|
68
|
-
|
|
69
71
|
this.wireframeMaterial = new THREE.LineBasicMaterial({
|
|
70
72
|
color: SCI_FI_CYAN,
|
|
71
73
|
transparent: true,
|
|
@@ -83,9 +85,27 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
83
85
|
})
|
|
84
86
|
}
|
|
85
87
|
|
|
88
|
+
/** Read live config — option may sync after module construction (watchOptions). */
|
|
89
|
+
isFuturisticRevealConfigured (): boolean {
|
|
90
|
+
return this.worldRenderer.worldRendererConfig.futuristicReveal === true
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Until the first cinematic wave fully completes, shader sections stay off the global buffer. */
|
|
94
|
+
isInInitialRevealCampaign (): boolean {
|
|
95
|
+
return this.enabled && !this.initialRevealWaveSettled
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
autoEnableCheck (): boolean {
|
|
99
|
+
return this.isFuturisticRevealConfigured()
|
|
100
|
+
}
|
|
101
|
+
|
|
86
102
|
enable(): void {
|
|
87
|
-
if (!this.
|
|
88
|
-
if (this.enabled) return
|
|
103
|
+
if (!this.isFuturisticRevealConfigured()) return
|
|
104
|
+
if (this.enabled && this.patched) return
|
|
105
|
+
if (this.enabled && !this.patched) {
|
|
106
|
+
this.patchWorldRenderer()
|
|
107
|
+
return
|
|
108
|
+
}
|
|
89
109
|
this.enabled = true
|
|
90
110
|
this.patchWorldRenderer()
|
|
91
111
|
}
|
|
@@ -214,8 +234,7 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
214
234
|
* Check if an object or its children is a mesh that needs reveal effect visibility patch
|
|
215
235
|
*/
|
|
216
236
|
private checkAndPatchMesh(obj: THREE.Object3D): void {
|
|
217
|
-
|
|
218
|
-
if (obj instanceof THREE.Mesh && obj.name === 'mesh') {
|
|
237
|
+
if (obj instanceof THREE.Mesh && (obj.name === 'mesh' || obj.name === 'shaderMesh')) {
|
|
219
238
|
const sectionKey = this.findSectionKeyForMesh(obj)
|
|
220
239
|
if (sectionKey && this.shouldUseRevealEffect(sectionKey)) {
|
|
221
240
|
obj.visible = false
|
|
@@ -280,13 +299,62 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
280
299
|
return this.worldRenderer.getCameraPosition()
|
|
281
300
|
}
|
|
282
301
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
302
|
+
private sectionHasRevealContent(geometry: MesherGeometryOutput): boolean {
|
|
303
|
+
if ((geometry.wireframePositions?.length ?? 0) > 0) return true
|
|
304
|
+
if ((geometry.positions?.length ?? 0) > 0) return true
|
|
305
|
+
return (geometry.shaderCubes?.count ?? 0) > 0
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Legacy `mesh` and instanced `shaderMesh` for a section. */
|
|
309
|
+
private getSectionRenderMeshes(key: string): THREE.Mesh[] {
|
|
287
310
|
const sectionObject = this.worldRenderer.chunkMeshManager.sectionObjects[key]
|
|
288
|
-
if (!sectionObject) return
|
|
289
|
-
|
|
311
|
+
if (!sectionObject) return []
|
|
312
|
+
const meshes: THREE.Mesh[] = []
|
|
313
|
+
for (const name of ['mesh', 'shaderMesh'] as const) {
|
|
314
|
+
const child = sectionObject.children.find(c => c.name === name)
|
|
315
|
+
if (child instanceof THREE.Mesh) meshes.push(child)
|
|
316
|
+
}
|
|
317
|
+
return meshes
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private hideSectionRenderMeshes(key: string): THREE.Mesh[] {
|
|
321
|
+
const meshes = this.getSectionRenderMeshes(key)
|
|
322
|
+
for (const m of meshes) {
|
|
323
|
+
m.visible = false
|
|
324
|
+
;(m as any).hiddenByReveal = true
|
|
325
|
+
}
|
|
326
|
+
return meshes
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private setMeshFadeOpacity(mesh: THREE.Mesh, opacity: number): void {
|
|
330
|
+
// ShaderMaterial ignores material.opacity; cloning breaks atlas uniforms.
|
|
331
|
+
if (mesh.name === 'shaderMesh') {
|
|
332
|
+
mesh.visible = opacity > 0.001
|
|
333
|
+
return
|
|
334
|
+
}
|
|
335
|
+
const mat = mesh.material
|
|
336
|
+
if (Array.isArray(mat)) return
|
|
337
|
+
if (!(mat as any).originalMaterial) {
|
|
338
|
+
;(mat as any).originalMaterial = mat
|
|
339
|
+
const fadeMat = mat.clone()
|
|
340
|
+
fadeMat.transparent = true
|
|
341
|
+
fadeMat.opacity = opacity
|
|
342
|
+
fadeMat.needsUpdate = true
|
|
343
|
+
mesh.material = fadeMat
|
|
344
|
+
} else {
|
|
345
|
+
mat.opacity = opacity
|
|
346
|
+
mat.transparent = true
|
|
347
|
+
mat.needsUpdate = true
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
private restoreMeshMaterial(mesh: THREE.Mesh): void {
|
|
352
|
+
const originalMat = (mesh as any).originalMaterial as THREE.Material | undefined
|
|
353
|
+
if (!originalMat) return
|
|
354
|
+
const currentMat = mesh.material as THREE.Material
|
|
355
|
+
mesh.material = originalMat
|
|
356
|
+
if (currentMat !== originalMat) currentMat.dispose()
|
|
357
|
+
delete (mesh as any).originalMaterial
|
|
290
358
|
}
|
|
291
359
|
|
|
292
360
|
/**
|
|
@@ -307,6 +375,12 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
307
375
|
// If already revealed or currently revealing, skip
|
|
308
376
|
if (this.revealedChunks.has(key) || this.revealingSections.has(key)) return
|
|
309
377
|
|
|
378
|
+
// After the initial spawn wave, show streaming sections immediately (no wireframe flash).
|
|
379
|
+
if (this.revealTriggered && this.initialWaveDone) {
|
|
380
|
+
this.revealedChunks.add(key)
|
|
381
|
+
return
|
|
382
|
+
}
|
|
383
|
+
|
|
310
384
|
// If reveal already triggered, start effect immediately (don't store in pending)
|
|
311
385
|
if (this.revealTriggered) {
|
|
312
386
|
this.startSectionReveal(key, geometry)
|
|
@@ -320,7 +394,10 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
320
394
|
* Check if a section should use the reveal effect
|
|
321
395
|
*/
|
|
322
396
|
shouldUseRevealEffect(key: string): boolean {
|
|
323
|
-
|
|
397
|
+
if (!this.enabled) return false
|
|
398
|
+
// Match registerSection: no cinematic hide for chunks loaded while moving.
|
|
399
|
+
if (this.revealTriggered && this.initialWaveDone) return false
|
|
400
|
+
return !this.revealedChunks.has(key) && !this.revealingSections.has(key)
|
|
324
401
|
}
|
|
325
402
|
|
|
326
403
|
/**
|
|
@@ -368,7 +445,7 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
368
445
|
* Start the reveal effect for a single section
|
|
369
446
|
*/
|
|
370
447
|
private startSectionReveal(key: string, geometry: MesherGeometryOutput): void {
|
|
371
|
-
if (!geometry
|
|
448
|
+
if (!this.sectionHasRevealContent(geometry)) return
|
|
372
449
|
|
|
373
450
|
// Don't create if already exists
|
|
374
451
|
if (this.revealingSections.has(key) || this.revealedChunks.has(key)) return
|
|
@@ -376,11 +453,10 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
376
453
|
// Create wireframe geometry
|
|
377
454
|
const wireframeGeom = this.createWireframeGeometry(geometry)
|
|
378
455
|
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
}
|
|
456
|
+
const global = this.worldRenderer.chunkMeshManager.globalBlockBuffer
|
|
457
|
+
const globalShaderRestore = global?.hasSection(key) ? global.takeSectionData(key) : undefined
|
|
458
|
+
|
|
459
|
+
const renderMeshRefs = this.hideSectionRenderMeshes(key)
|
|
384
460
|
// Main wireframe
|
|
385
461
|
const wireframe = new THREE.LineSegments(wireframeGeom, this.wireframeMaterial.clone())
|
|
386
462
|
this.worldRenderer.sceneOrigin.track(wireframe)
|
|
@@ -413,25 +489,45 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
413
489
|
wireframeGroup: group,
|
|
414
490
|
revealStartTime: performance.now(),
|
|
415
491
|
phase: 'wireframe',
|
|
416
|
-
|
|
492
|
+
renderMeshRefs,
|
|
493
|
+
globalShaderRestore,
|
|
417
494
|
wireframeMs,
|
|
418
495
|
revealMs,
|
|
419
496
|
}
|
|
420
497
|
|
|
421
498
|
setTimeout(() => {
|
|
422
|
-
const m
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
499
|
+
for (const m of this.getSectionRenderMeshes(key)) {
|
|
500
|
+
if (!(m as any).hiddenByReveal) {
|
|
501
|
+
this.hideSectionRenderMeshes(key)
|
|
502
|
+
}
|
|
426
503
|
}
|
|
427
504
|
}, 0)
|
|
428
505
|
|
|
429
506
|
this.revealingSections.set(key, section)
|
|
430
507
|
}
|
|
431
508
|
|
|
432
|
-
/**
|
|
433
|
-
|
|
434
|
-
|
|
509
|
+
/** 16³ section bounds wireframe in section-local coords (−8…+8). */
|
|
510
|
+
private createSectionBoundsWireframe(): THREE.BufferGeometry {
|
|
511
|
+
const min = -8
|
|
512
|
+
const max = 8
|
|
513
|
+
const c = [
|
|
514
|
+
[min, min, min], [max, min, min], [min, max, min], [max, max, min],
|
|
515
|
+
[min, min, max], [max, min, max], [min, max, max], [max, max, max],
|
|
516
|
+
] as const
|
|
517
|
+
const edges: [number, number][] = [
|
|
518
|
+
[0, 1], [1, 3], [3, 2], [2, 0],
|
|
519
|
+
[4, 5], [5, 7], [7, 6], [6, 4],
|
|
520
|
+
[0, 4], [1, 5], [2, 6], [3, 7],
|
|
521
|
+
]
|
|
522
|
+
const linePositions: number[] = []
|
|
523
|
+
for (const [a, b] of edges) {
|
|
524
|
+
linePositions.push(...c[a]!, ...c[b]!)
|
|
525
|
+
}
|
|
526
|
+
const wireframeGeom = new THREE.BufferGeometry()
|
|
527
|
+
wireframeGeom.setAttribute('position', new THREE.Float32BufferAttribute(linePositions, 3))
|
|
528
|
+
return wireframeGeom
|
|
529
|
+
}
|
|
530
|
+
|
|
435
531
|
private createWireframeGeometry(geometry: MesherGeometryOutput): THREE.BufferGeometry {
|
|
436
532
|
if (geometry.wireframePositions && geometry.wireframePositions.length > 0) {
|
|
437
533
|
const wireframeGeom = new THREE.BufferGeometry()
|
|
@@ -442,6 +538,10 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
442
538
|
const positions = geometry.positions as Float32Array
|
|
443
539
|
const indices = geometry.indices as Uint32Array | Uint16Array
|
|
444
540
|
|
|
541
|
+
if (!positions?.length || !indices?.length) {
|
|
542
|
+
return this.createSectionBoundsWireframe()
|
|
543
|
+
}
|
|
544
|
+
|
|
445
545
|
const linePositions: number[] = []
|
|
446
546
|
const edgeSet = new Set<string>()
|
|
447
547
|
|
|
@@ -529,18 +629,10 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
529
629
|
if (elapsed > section.wireframeMs) {
|
|
530
630
|
section.phase = 'transitioning'
|
|
531
631
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
// Store original material and create fade version
|
|
537
|
-
const originalMat = section.originalMeshRef.material as THREE.MeshLambertMaterial
|
|
538
|
-
const fadeMat = originalMat.clone()
|
|
539
|
-
fadeMat.transparent = true
|
|
540
|
-
fadeMat.opacity = 0
|
|
541
|
-
fadeMat.needsUpdate = true
|
|
542
|
-
; (section.originalMeshRef as any).originalMaterial = originalMat
|
|
543
|
-
section.originalMeshRef.material = fadeMat
|
|
632
|
+
section.renderMeshRefs = this.getSectionRenderMeshes(key)
|
|
633
|
+
for (const mesh of section.renderMeshRefs) {
|
|
634
|
+
mesh.visible = true
|
|
635
|
+
this.setMeshFadeOpacity(mesh, 0)
|
|
544
636
|
}
|
|
545
637
|
}
|
|
546
638
|
} else if (section.phase === 'transitioning') {
|
|
@@ -564,10 +656,8 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
564
656
|
glowMat.opacity = (1 - eased) * 0.55
|
|
565
657
|
}
|
|
566
658
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
const fadeMat = section.originalMeshRef.material as THREE.MeshLambertMaterial
|
|
570
|
-
fadeMat.opacity = eased
|
|
659
|
+
for (const mesh of section.renderMeshRefs) {
|
|
660
|
+
this.setMeshFadeOpacity(mesh, eased)
|
|
571
661
|
}
|
|
572
662
|
|
|
573
663
|
// Complete transition
|
|
@@ -592,17 +682,24 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
592
682
|
this.revealingSections.delete(section.key)
|
|
593
683
|
this.revealedChunks.add(section.key)
|
|
594
684
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
685
|
+
if (this.revealTriggered && this.revealingSections.size === 0) {
|
|
686
|
+
this.initialRevealWaveSettled = true
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
for (const mesh of section.renderMeshRefs) {
|
|
690
|
+
this.restoreMeshMaterial(mesh)
|
|
691
|
+
mesh.visible = true
|
|
692
|
+
delete (mesh as any).hiddenByReveal
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
this.worldRenderer.chunkMeshManager.migrateDeferredShaderToGlobal(section.key)
|
|
696
|
+
|
|
697
|
+
if (section.globalShaderRestore) {
|
|
698
|
+
this.worldRenderer.chunkMeshManager.globalBlockBuffer?.addSection(
|
|
699
|
+
section.key,
|
|
700
|
+
section.globalShaderRestore.words,
|
|
701
|
+
section.globalShaderRestore.count,
|
|
702
|
+
)
|
|
606
703
|
}
|
|
607
704
|
|
|
608
705
|
// Clean up wireframe group
|
|
@@ -655,6 +752,8 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
655
752
|
this.revealedChunks.clear()
|
|
656
753
|
this.finishedChunkCount = 0
|
|
657
754
|
this.revealTriggered = false
|
|
755
|
+
this.initialWaveDone = false
|
|
756
|
+
this.initialRevealWaveSettled = false
|
|
658
757
|
this.revealStartTime = 0
|
|
659
758
|
this.pulseTime = 0
|
|
660
759
|
}
|
|
@@ -665,16 +764,11 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
665
764
|
forceCompleteAll(): void {
|
|
666
765
|
const sections = [...this.revealingSections.values()]
|
|
667
766
|
for (const section of sections) {
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
const originalMat = (section.originalMeshRef as any).originalMaterial
|
|
674
|
-
if (originalMat) {
|
|
675
|
-
section.originalMeshRef.material = originalMat
|
|
676
|
-
}
|
|
677
|
-
section.originalMeshRef.visible = true
|
|
767
|
+
section.renderMeshRefs = this.getSectionRenderMeshes(section.key)
|
|
768
|
+
for (const mesh of section.renderMeshRefs) {
|
|
769
|
+
this.restoreMeshMaterial(mesh)
|
|
770
|
+
mesh.visible = true
|
|
771
|
+
delete (mesh as any).hiddenByReveal
|
|
678
772
|
}
|
|
679
773
|
this.completeReveal(section)
|
|
680
774
|
}
|
|
@@ -736,7 +830,7 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
736
830
|
sections: [...this.revealingSections.entries()].map(([key, s]) => ({
|
|
737
831
|
key,
|
|
738
832
|
phase: s.phase,
|
|
739
|
-
|
|
833
|
+
renderMeshCount: s.renderMeshRefs.length,
|
|
740
834
|
wireframeInScene: s.wireframeGroup.parent !== null
|
|
741
835
|
}))
|
|
742
836
|
}
|
|
@@ -139,10 +139,18 @@ export class StarfieldModule implements RendererModuleController {
|
|
|
139
139
|
|
|
140
140
|
const material = new StarfieldMaterial()
|
|
141
141
|
material.blending = THREE.AdditiveBlending
|
|
142
|
-
|
|
142
|
+
// depthTest=true so opaque blocks in front correctly occlude stars (was false, which
|
|
143
|
+
// made them additively visible THROUGH every opaque mesh — most visible against the
|
|
144
|
+
// new shader-cube path which renders in the opaque queue). depthWrite=false keeps
|
|
145
|
+
// stars from clobbering the depth buffer for any transparent meshes drawn after
|
|
146
|
+
// them in the same pass (clouds, foliage cutouts, particles…).
|
|
147
|
+
material.depthTest = true
|
|
148
|
+
material.depthWrite = false
|
|
143
149
|
material.transparent = true
|
|
144
150
|
|
|
145
151
|
this.points = new THREE.Points(geometry, material)
|
|
152
|
+
// renderOrder=-1 keeps stars at the FRONT of the transparent queue, so any
|
|
153
|
+
// transparent foreground (leaves, glass, water) still composites on top of them.
|
|
146
154
|
this.points.renderOrder = -1
|
|
147
155
|
this.worldRenderer.scene.add(this.points)
|
|
148
156
|
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { SHADER_CUBES_WORDS_PER_FACE } from '../wasm-mesher/bridge/shaderCubeBridge'
|
|
3
|
+
import { WORD0 } from './shaders/cubeBlockShader'
|
|
4
|
+
|
|
5
|
+
export type ShaderSectionRaycastBox = {
|
|
6
|
+
minX: number
|
|
7
|
+
minY: number
|
|
8
|
+
minZ: number
|
|
9
|
+
maxX: number
|
|
10
|
+
maxY: number
|
|
11
|
+
maxZ: number
|
|
12
|
+
cx: number
|
|
13
|
+
cy: number
|
|
14
|
+
cz: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Tight world-space AABB covering occupied shader-cube blocks in a section.
|
|
19
|
+
* `sectionCenter*` is geometryData.sx/sy/sz (section base + 8).
|
|
20
|
+
*/
|
|
21
|
+
export function computeShaderSectionRaycastAabb (
|
|
22
|
+
words: Uint32Array,
|
|
23
|
+
faceCount: number,
|
|
24
|
+
sectionCenterX: number,
|
|
25
|
+
sectionCenterY: number,
|
|
26
|
+
sectionCenterZ: number,
|
|
27
|
+
): ShaderSectionRaycastBox | undefined {
|
|
28
|
+
if (faceCount <= 0) return undefined
|
|
29
|
+
|
|
30
|
+
const baseX = sectionCenterX - 8
|
|
31
|
+
const baseY = sectionCenterY - 8
|
|
32
|
+
const baseZ = sectionCenterZ - 8
|
|
33
|
+
const stride = SHADER_CUBES_WORDS_PER_FACE
|
|
34
|
+
|
|
35
|
+
let minLx = 16
|
|
36
|
+
let minLy = 16
|
|
37
|
+
let minLz = 16
|
|
38
|
+
let maxLx = -1
|
|
39
|
+
let maxLy = -1
|
|
40
|
+
let maxLz = -1
|
|
41
|
+
|
|
42
|
+
for (let i = 0; i < faceCount; i++) {
|
|
43
|
+
const w0 = words[i * stride]!
|
|
44
|
+
const lx = w0 & ((1 << WORD0.LX_BITS) - 1)
|
|
45
|
+
const ly = (w0 >> WORD0.LY_SHIFT) & ((1 << WORD0.LY_BITS) - 1)
|
|
46
|
+
const lz = (w0 >> WORD0.LZ_SHIFT) & ((1 << WORD0.LZ_BITS) - 1)
|
|
47
|
+
if (lx < minLx) minLx = lx
|
|
48
|
+
if (ly < minLy) minLy = ly
|
|
49
|
+
if (lz < minLz) minLz = lz
|
|
50
|
+
if (lx > maxLx) maxLx = lx
|
|
51
|
+
if (ly > maxLy) maxLy = ly
|
|
52
|
+
if (lz > maxLz) maxLz = lz
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (maxLx < 0) return undefined
|
|
56
|
+
|
|
57
|
+
const minX = baseX + minLx
|
|
58
|
+
const minY = baseY + minLy
|
|
59
|
+
const minZ = baseZ + minLz
|
|
60
|
+
const maxX = baseX + maxLx + 1
|
|
61
|
+
const maxY = baseY + maxLy + 1
|
|
62
|
+
const maxZ = baseZ + maxLz + 1
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
minX,
|
|
66
|
+
minY,
|
|
67
|
+
minZ,
|
|
68
|
+
maxX,
|
|
69
|
+
maxY,
|
|
70
|
+
maxZ,
|
|
71
|
+
cx: (minX + maxX) * 0.5,
|
|
72
|
+
cy: (minY + maxY) * 0.5,
|
|
73
|
+
cz: (minZ + maxZ) * 0.5,
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isPointInsideAabb (
|
|
78
|
+
ox: number,
|
|
79
|
+
oy: number,
|
|
80
|
+
oz: number,
|
|
81
|
+
minX: number,
|
|
82
|
+
minY: number,
|
|
83
|
+
minZ: number,
|
|
84
|
+
maxX: number,
|
|
85
|
+
maxY: number,
|
|
86
|
+
maxZ: number,
|
|
87
|
+
): boolean {
|
|
88
|
+
return ox >= minX && ox <= maxX && oy >= minY && oy <= maxY && oz >= minZ && oz <= maxZ
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Ray–AABB entry distance, or undefined. Ignores hits when origin is inside the box. */
|
|
92
|
+
export function raycastAabb (
|
|
93
|
+
ox: number,
|
|
94
|
+
oy: number,
|
|
95
|
+
oz: number,
|
|
96
|
+
dx: number,
|
|
97
|
+
dy: number,
|
|
98
|
+
dz: number,
|
|
99
|
+
minX: number,
|
|
100
|
+
minY: number,
|
|
101
|
+
minZ: number,
|
|
102
|
+
maxX: number,
|
|
103
|
+
maxY: number,
|
|
104
|
+
maxZ: number,
|
|
105
|
+
maxDist: number,
|
|
106
|
+
): number | undefined {
|
|
107
|
+
if (isPointInsideAabb(ox, oy, oz, minX, minY, minZ, maxX, maxY, maxZ)) {
|
|
108
|
+
return undefined
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let tmin = 0
|
|
112
|
+
let tmax = maxDist
|
|
113
|
+
|
|
114
|
+
if (Math.abs(dx) < 1e-8) {
|
|
115
|
+
if (ox < minX || ox > maxX) return undefined
|
|
116
|
+
} else {
|
|
117
|
+
const inv = 1 / dx
|
|
118
|
+
let t1 = (minX - ox) * inv
|
|
119
|
+
let t2 = (maxX - ox) * inv
|
|
120
|
+
if (t1 > t2) { const tmp = t1; t1 = t2; t2 = tmp }
|
|
121
|
+
tmin = Math.max(tmin, t1)
|
|
122
|
+
tmax = Math.min(tmax, t2)
|
|
123
|
+
if (tmin > tmax) return undefined
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (Math.abs(dy) < 1e-8) {
|
|
127
|
+
if (oy < minY || oy > maxY) return undefined
|
|
128
|
+
} else {
|
|
129
|
+
const inv = 1 / dy
|
|
130
|
+
let t1 = (minY - oy) * inv
|
|
131
|
+
let t2 = (maxY - oy) * inv
|
|
132
|
+
if (t1 > t2) { const tmp = t1; t1 = t2; t2 = tmp }
|
|
133
|
+
tmin = Math.max(tmin, t1)
|
|
134
|
+
tmax = Math.min(tmax, t2)
|
|
135
|
+
if (tmin > tmax) return undefined
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (Math.abs(dz) < 1e-8) {
|
|
139
|
+
if (oz < minZ || oz > maxZ) return undefined
|
|
140
|
+
} else {
|
|
141
|
+
const inv = 1 / dz
|
|
142
|
+
let t1 = (minZ - oz) * inv
|
|
143
|
+
let t2 = (maxZ - oz) * inv
|
|
144
|
+
if (t1 > t2) { const tmp = t1; t1 = t2; t2 = tmp }
|
|
145
|
+
tmin = Math.max(tmin, t1)
|
|
146
|
+
tmax = Math.min(tmax, t2)
|
|
147
|
+
if (tmin > tmax) return undefined
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return tmin <= tmax && tmin >= 0 ? tmin : undefined
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** 16³ section box centered at (cx, cy, cz) — tests / legacy helper. */
|
|
154
|
+
export function raycastSectionAabb (
|
|
155
|
+
ox: number,
|
|
156
|
+
oy: number,
|
|
157
|
+
oz: number,
|
|
158
|
+
dx: number,
|
|
159
|
+
dy: number,
|
|
160
|
+
dz: number,
|
|
161
|
+
cx: number,
|
|
162
|
+
cy: number,
|
|
163
|
+
cz: number,
|
|
164
|
+
maxDist: number,
|
|
165
|
+
): number | undefined {
|
|
166
|
+
return raycastAabb(ox, oy, oz, dx, dy, dz, cx - 8, cy - 8, cz - 8, cx + 8, cy + 8, cz + 8, maxDist)
|
|
167
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import * as THREE from 'three'
|
|
3
|
+
import { VERTICES_PER_FACE } from './shaders/cubeBlockShader'
|
|
4
|
+
import { SHADER_CUBES_WORDS_PER_FACE } from '../wasm-mesher/bridge/shaderCubeBridge'
|
|
5
|
+
|
|
6
|
+
export type ShaderCubeInstanceData = {
|
|
7
|
+
words: Uint32Array
|
|
8
|
+
count: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Build InstancedBufferGeometry for full-cube shader faces.
|
|
13
|
+
* One instance = one visible face; vertex shader uses gl_VertexID (6 verts/face).
|
|
14
|
+
*/
|
|
15
|
+
export function buildShaderCubeGeometry(
|
|
16
|
+
words: Uint32Array,
|
|
17
|
+
faceCount: number,
|
|
18
|
+
): THREE.InstancedBufferGeometry {
|
|
19
|
+
const geometry = new THREE.InstancedBufferGeometry()
|
|
20
|
+
|
|
21
|
+
const positions = new Float32Array(VERTICES_PER_FACE * 3)
|
|
22
|
+
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
|
|
23
|
+
|
|
24
|
+
const w0 = new Uint32Array(faceCount)
|
|
25
|
+
const w1 = new Uint32Array(faceCount)
|
|
26
|
+
const w2 = new Uint32Array(faceCount)
|
|
27
|
+
const w3 = new Uint32Array(faceCount)
|
|
28
|
+
const stride = SHADER_CUBES_WORDS_PER_FACE
|
|
29
|
+
for (let i = 0; i < faceCount; i++) {
|
|
30
|
+
w0[i] = words[i * stride]!
|
|
31
|
+
w1[i] = words[i * stride + 1]!
|
|
32
|
+
w2[i] = words[i * stride + 2]!
|
|
33
|
+
w3[i] = words[i * stride + 3]!
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
geometry.setAttribute('a_w0', new THREE.InstancedBufferAttribute(w0, 1))
|
|
37
|
+
geometry.setAttribute('a_w1', new THREE.InstancedBufferAttribute(w1, 1))
|
|
38
|
+
geometry.setAttribute('a_w2', new THREE.InstancedBufferAttribute(w2, 1))
|
|
39
|
+
geometry.setAttribute('a_w3', new THREE.InstancedBufferAttribute(w3, 1))
|
|
40
|
+
|
|
41
|
+
geometry.instanceCount = faceCount
|
|
42
|
+
geometry.boundingBox = new THREE.Box3(
|
|
43
|
+
new THREE.Vector3(-8, -8, -8),
|
|
44
|
+
new THREE.Vector3(8, 8, 8),
|
|
45
|
+
)
|
|
46
|
+
geometry.boundingSphere = new THREE.Sphere(
|
|
47
|
+
new THREE.Vector3(0, 0, 0),
|
|
48
|
+
Math.sqrt(3 * 8 ** 2),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return geometry
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const _raycastBox = new THREE.Box3()
|
|
55
|
+
const _raycastPoint = new THREE.Vector3()
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* CPU raycast uses section AABB (geometry.boundingBox), not GPU-generated faces.
|
|
59
|
+
* Enough for third-person camera collision; block pick uses mineflayer, not mesh raycast.
|
|
60
|
+
*/
|
|
61
|
+
export function attachShaderCubeRaycast(
|
|
62
|
+
mesh: THREE.Mesh<THREE.BufferGeometry, THREE.Material>,
|
|
63
|
+
): void {
|
|
64
|
+
mesh.raycast = (raycaster, intersects) => {
|
|
65
|
+
const { geometry } = mesh
|
|
66
|
+
if (!geometry.boundingBox) return
|
|
67
|
+
_raycastBox.copy(geometry.boundingBox).applyMatrix4(mesh.matrixWorld)
|
|
68
|
+
if (!raycaster.ray.intersectBox(_raycastBox, _raycastPoint)) return
|
|
69
|
+
const distance = raycaster.ray.origin.distanceTo(_raycastPoint)
|
|
70
|
+
intersects.push({
|
|
71
|
+
distance,
|
|
72
|
+
point: _raycastPoint.clone(),
|
|
73
|
+
object: mesh,
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function createShaderCubeMesh(
|
|
79
|
+
data: ShaderCubeInstanceData,
|
|
80
|
+
material: THREE.ShaderMaterial,
|
|
81
|
+
): THREE.Mesh<THREE.InstancedBufferGeometry, THREE.ShaderMaterial> {
|
|
82
|
+
const geometry = buildShaderCubeGeometry(data.words, data.count)
|
|
83
|
+
const mesh = new THREE.Mesh(geometry, material)
|
|
84
|
+
mesh.name = 'shaderMesh'
|
|
85
|
+
mesh.matrixAutoUpdate = false
|
|
86
|
+
mesh.frustumCulled = false
|
|
87
|
+
attachShaderCubeRaycast(mesh)
|
|
88
|
+
return mesh
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function disposeShaderCubeMesh(mesh: THREE.Mesh): void {
|
|
92
|
+
mesh.geometry.dispose()
|
|
93
|
+
}
|