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.
Files changed (34) hide show
  1. package/dist/mesher.js +1 -1
  2. package/dist/mesher.js.map +2 -2
  3. package/dist/mesherWasm.js +3740 -183
  4. package/dist/minecraft-renderer.js +332 -60
  5. package/dist/minecraft-renderer.js.meta.json +1 -1
  6. package/dist/threeWorker.js +705 -433
  7. package/package.json +1 -1
  8. package/src/graphicsBackend/config.ts +4 -0
  9. package/src/graphicsBackend/playerState.ts +1 -0
  10. package/src/graphicsBackend/rendererOptionsSync.ts +1 -0
  11. package/src/lib/worldrendererCommon.ts +13 -0
  12. package/src/mesher-shared/exportedGeometryTypes.ts +5 -1
  13. package/src/mesher-shared/shared.ts +8 -0
  14. package/src/playerState/playerState.ts +2 -0
  15. package/src/three/chunkMeshManager.ts +312 -39
  16. package/src/three/globalBlockBuffer.ts +292 -0
  17. package/src/three/menuBackground/config.ts +1 -1
  18. package/src/three/menuBackground/defaultOptions.ts +27 -19
  19. package/src/three/modules/sciFiWorldReveal.ts +162 -68
  20. package/src/three/modules/starfield.ts +9 -1
  21. package/src/three/sectionRaycastAabb.ts +167 -0
  22. package/src/three/shaderCubeMesh.ts +93 -0
  23. package/src/three/shaders/cubeBlockShader.ts +354 -0
  24. package/src/three/shaders/textureIndexMapping.ts +122 -0
  25. package/src/three/shaders/tintPalette.ts +198 -0
  26. package/src/three/worldGeometryExport.ts +53 -25
  27. package/src/three/worldRendererThree.ts +93 -26
  28. package/src/wasm-mesher/bridge/render-from-wasm.ts +62 -185
  29. package/src/wasm-mesher/bridge/shaderCubeBridge.ts +396 -0
  30. package/src/wasm-mesher/runtime-build/wasm_mesher_bg.wasm +0 -0
  31. package/src/wasm-mesher/tests/sectionRaycastAabb.test.ts +58 -0
  32. package/src/wasm-mesher/tests/shaderCubeInstances.test.ts +360 -0
  33. package/src/wasm-mesher/tests/splitColumnWasmOutput.test.ts +11 -4
  34. 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
- originalMeshRef: THREE.Mesh | null
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.configEnabled) return
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
- // Check if this is a mesh with name === 'mesh'
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
- * Get original mesh for a section key
285
- */
286
- private getOriginalMesh(key: string): THREE.Mesh | null {
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 null
289
- return sectionObject.children.find(child => child.name === 'mesh') as THREE.Mesh | null
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
- return this.enabled && !this.revealedChunks.has(key) && !this.revealingSections.has(key)
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.positions?.length) return
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 original = this.getOriginalMesh(key)
380
- if (original) {
381
- original.visible = false
382
- ; (original as any).hiddenByReveal = true
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
- originalMeshRef: null,
492
+ renderMeshRefs,
493
+ globalShaderRestore,
417
494
  wireframeMs,
418
495
  revealMs,
419
496
  }
420
497
 
421
498
  setTimeout(() => {
422
- const m = this.getOriginalMesh(key)
423
- if (m && !(m as any).hiddenByReveal) {
424
- m.visible = false
425
- ; (m as any).hiddenByReveal = true
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
- * Create wireframe geometry from mesh geometry
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
- // Get and show the original mesh with fade-in
533
- section.originalMeshRef = this.getOriginalMesh(key)
534
- if (section.originalMeshRef) {
535
- section.originalMeshRef.visible = true
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
- // Fade in original mesh
568
- if (section.originalMeshRef?.material) {
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
- // Restore original material first
596
- if (section.originalMeshRef) {
597
- const originalMat = (section.originalMeshRef as any).originalMaterial
598
- if (originalMat) {
599
- const currentMat = section.originalMeshRef.material as THREE.Material
600
- section.originalMeshRef.material = originalMat
601
- currentMat.dispose()
602
- delete (section.originalMeshRef as any).originalMaterial
603
- }
604
- section.originalMeshRef.visible = true
605
- delete (section.originalMeshRef as any).hiddenByReveal
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
- // Show original mesh immediately
669
- if (!section.originalMeshRef) {
670
- section.originalMeshRef = this.getOriginalMesh(section.key)
671
- }
672
- if (section.originalMeshRef) {
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
- hasOriginalMesh: !!s.originalMeshRef,
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
- material.depthTest = false
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
+ }