minecraft-renderer 0.1.72 → 0.1.73

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 (62) hide show
  1. package/README.md +1 -1
  2. package/dist/mesher.js +81 -81
  3. package/dist/mesher.js.map +3 -3
  4. package/dist/mesherWasm.js +1183 -943
  5. package/dist/minecraft-renderer.js +249 -78
  6. package/dist/minecraft-renderer.js.meta.json +1 -1
  7. package/dist/threeWorker.js +1732 -1001
  8. package/package.json +3 -3
  9. package/src/graphicsBackend/rendererDefaultOptions.ts +2 -7
  10. package/src/graphicsBackend/rendererOptionsSync.ts +1 -1
  11. package/src/lib/bakeLegacyLight.ts +17 -0
  12. package/src/lib/blockEntityLightRegistry.test.ts +18 -0
  13. package/src/lib/blockEntityLightRegistry.ts +75 -0
  14. package/src/lib/blockEntityLighting.test.ts +30 -0
  15. package/src/lib/blockEntityLighting.ts +53 -0
  16. package/src/lib/worldrendererCommon.ts +14 -6
  17. package/src/mesher-shared/blockEntityMetadata.test.ts +33 -0
  18. package/src/mesher-shared/blockEntityMetadata.ts +19 -3
  19. package/src/mesher-shared/exportedGeometryTypes.ts +11 -0
  20. package/src/mesher-shared/models.ts +161 -92
  21. package/src/mesher-shared/shared.ts +15 -4
  22. package/src/mesher-shared/tests/liquidQuadInvariant.test.ts +40 -0
  23. package/src/mesher-shared/world.ts +12 -0
  24. package/src/mesher-shared/worldLighting.test.ts +54 -0
  25. package/src/playground/baseScene.ts +1 -1
  26. package/src/three/bannerRenderer.ts +10 -3
  27. package/src/three/chunkMeshManager.ts +663 -69
  28. package/src/three/cubeDrawSpans.ts +74 -0
  29. package/src/three/cubeMultiDraw.ts +119 -0
  30. package/src/three/documentRenderer.ts +0 -2
  31. package/src/three/entities.ts +5 -6
  32. package/src/three/entity/EntityMesh.ts +7 -5
  33. package/src/three/entity/gltfAnimationUtils.ts +5 -3
  34. package/src/three/globalBlockBuffer.ts +208 -12
  35. package/src/three/globalLegacyBuffer.ts +701 -0
  36. package/src/three/itemMesh.ts +5 -2
  37. package/src/three/legacySectionCull.ts +85 -0
  38. package/src/three/modules/sciFiWorldReveal.ts +347 -703
  39. package/src/three/modules/starfield.ts +3 -2
  40. package/src/three/sectionRaycastAabb.ts +25 -0
  41. package/src/three/shaders/cubeBlockShader.ts +80 -17
  42. package/src/three/shaders/legacyBlockShader.ts +292 -0
  43. package/src/three/skyboxRenderer.ts +1 -1
  44. package/src/three/tests/chunkMeshManagerLegacy.test.ts +286 -0
  45. package/src/three/tests/cubeDrawSpans.test.ts +73 -0
  46. package/src/three/tests/globalLegacyBuffer.test.ts +360 -0
  47. package/src/three/tests/legacySectionCull.test.ts +80 -0
  48. package/src/three/tests/signTextureCache.test.ts +83 -0
  49. package/src/three/threeJsMedia.ts +2 -2
  50. package/src/three/waypointSprite.ts +2 -2
  51. package/src/three/world/cursorBlock.ts +1 -0
  52. package/src/three/world/vr.ts +2 -2
  53. package/src/three/worldGeometryExport.ts +83 -26
  54. package/src/three/worldRendererThree.ts +94 -25
  55. package/src/wasm-mesher/bridge/render-from-wasm.ts +214 -72
  56. package/src/wasm-mesher/bridge/shaderCubeBridge.ts +18 -6
  57. package/src/wasm-mesher/runtime-build/wasm_mesher_bg.wasm +0 -0
  58. package/src/wasm-mesher/tests/sectionRaycastAabb.test.ts +20 -0
  59. package/src/wasm-mesher/tests/shaderCubeInstances.test.ts +67 -5
  60. package/src/wasm-mesher/worker/mesherWasm.ts +70 -14
  61. package/src/wasm-mesher/worker/mesherWasmLightDirty.test.ts +11 -0
  62. package/src/wasm-mesher/worker/mesherWasmLightDirty.ts +15 -0
@@ -5,14 +5,19 @@ import * as nbt from 'prismarine-nbt'
5
5
  import { Vec3 } from 'vec3'
6
6
  import { MesherGeometryOutput } from '../mesher-shared/shared'
7
7
  import { getShaderCubeResources, SHADER_CUBES_WORDS_PER_FACE } from '../wasm-mesher/bridge/shaderCubeBridge'
8
- import { createCubeBlockMaterial } from './shaders/cubeBlockShader'
8
+ import { createCubeBlockMaterial, computeSectionOriginRel, setCubeSkyLevel, setCubeLightmapParams, type BlockLightmapParams } from './shaders/cubeBlockShader'
9
+ import { computeCameraRelativeUniforms, createGlobalLegacyBlendMaterial, createGlobalLegacyBlockMaterial, createLegacyBlockMaterial, setLegacyCameraOrigin, setLegacySkyLevel, setLegacyLightmapParams, type RenderOrigin } from './shaders/legacyBlockShader'
10
+ import { LEGACY_SECTION_HALF_EXTENT, sectionIntersectsFrustum, setupLegacySectionMatrix, updateLegacySectionCullState } from './legacySectionCull'
9
11
  import { createShaderCubeMesh, disposeShaderCubeMesh } from './shaderCubeMesh'
10
12
  import { GlobalBlockBuffer } from './globalBlockBuffer'
13
+ import { buildVisibleCubeSpans } from './cubeDrawSpans'
14
+ import { GlobalLegacyBuffer, type LegacySectionGeometry } from './globalLegacyBuffer'
11
15
  import {
12
16
  computeShaderSectionRaycastAabb,
13
17
  isPointInsideAabb,
14
18
  raycastAabb,
15
19
  raycastShaderBlocksAabb,
20
+ sectionAabbIntersectsRay,
16
21
  type ShaderSectionRaycastEntry,
17
22
  } from './sectionRaycastAabb'
18
23
  import { chunkPos } from '../lib/simpleUtils'
@@ -22,6 +27,7 @@ import type { WorldRendererThree } from './worldRendererThree'
22
27
  import { armorModel } from './entity/armorModels'
23
28
  import { disposeObject } from './threeJsUtils'
24
29
  import { getBannerTexture, createBannerMesh, releaseBannerTexture } from './bannerRenderer'
30
+ import { BlockEntityLightRegistry } from '../lib/blockEntityLightRegistry'
25
31
 
26
32
  export interface ChunkMeshPool {
27
33
  mesh: THREE.Mesh
@@ -36,6 +42,12 @@ export interface SectionObject extends THREE.Group {
36
42
  shaderMesh?: THREE.Mesh<THREE.InstancedBufferGeometry, THREE.ShaderMaterial>
37
43
  /** Shader cube words kept for migration to global buffer after reveal. */
38
44
  deferredShaderCubes?: { words: Uint32Array, count: number }
45
+ /** Opaque legacy geometry deferred from global buffer during sci-fi reveal. */
46
+ deferredLegacyOpaque?: LegacySectionGeometry
47
+ /** Blend legacy geometry deferred from global buffer during sci-fi reveal. */
48
+ deferredLegacyBlend?: LegacySectionGeometry
49
+ /** Section uses a pooled mesh for blend (reveal defer or invariant fallback). */
50
+ hasBlendMesh?: boolean
39
51
  tilesCount?: number
40
52
  blocksCount?: number
41
53
 
@@ -63,6 +75,11 @@ export interface SectionObject extends THREE.Group {
63
75
  }
64
76
 
65
77
  export class ChunkMeshManager {
78
+ private static readonly REBASE_THRESHOLD = 65536
79
+
80
+ /** Float64 render origin snapped to section granularity; GPU buffers store origins relative to this. */
81
+ private renderOrigin: RenderOrigin = { x: 0, y: 0, z: 0 }
82
+
66
83
  private readonly meshPool: ChunkMeshPool[] = []
67
84
  private readonly activeSections = new Map<string, ChunkMeshPool>()
68
85
  readonly sectionObjects: Record<string, SectionObject> = {}
@@ -90,6 +107,7 @@ export class ChunkMeshManager {
90
107
  private maxPoolSize!: number
91
108
  private minPoolSize!: number
92
109
  private readonly signHeadsRenderer: SignHeadsRenderer
110
+ private readonly blockEntityLightRegistry = new BlockEntityLightRegistry()
93
111
  /**
94
112
  * Shared transparent material used as the basis for the wireframe chunk
95
113
  * border `BoxHelper` created lazily in {@link updateBoxHelper}. Kept on the
@@ -99,8 +117,26 @@ export class ChunkMeshManager {
99
117
  private readonly chunkBoxMaterial = new THREE.MeshBasicMaterial({ color: 0x00_00_00, transparent: true, opacity: 0 })
100
118
  /** Shared across all sections — atlas/tint uniforms updated via {@link syncCubeShaderUniforms}. */
101
119
  private cubeShaderMaterial: THREE.ShaderMaterial | null = null
120
+ /** Per-section blend meshes — atlas + camera origin updated each frame. */
121
+ private legacyShaderMaterial: THREE.ShaderMaterial | null = null
122
+ private globalLegacyShaderMaterial: THREE.ShaderMaterial | null = null
123
+ private globalLegacyBlendShaderMaterial: THREE.ShaderMaterial | null = null
124
+ private readonly _legacyCullFrustum = new THREE.Frustum()
125
+ private readonly _legacyCullProjScreen = new THREE.Matrix4()
126
+ private readonly _legacyCullBox = new THREE.Box3()
127
+ private readonly _legacyCullBoxMin = new THREE.Vector3()
128
+ private readonly _legacyCullBoxMax = new THREE.Vector3()
129
+ private readonly _visibleSectionSpans: Array<{ key: string, distSq: number }> = []
130
+ /** Drives per-frame cull + span rebuild; cleared after updateSectionCullAndSort. */
131
+ cullDirty = true
132
+ private readonly _lastCullCamPos = new THREE.Vector3()
133
+ private readonly _lastCullCamQuat = new THREE.Quaternion()
134
+ private readonly _cullViewQuat = new THREE.Quaternion()
135
+ private _cullCamInitialized = false
102
136
  /** One instanced mesh for all shader-cube faces (single draw call). */
103
137
  globalBlockBuffer: GlobalBlockBuffer | null = null
138
+ globalLegacyBuffer: GlobalLegacyBuffer | null = null
139
+ globalLegacyBlendBuffer: GlobalLegacyBuffer | null = null
104
140
  /** Tight world AABBs for third-person raycast; block word0 read from GlobalBlockBuffer or deferred. */
105
141
  private readonly shaderSectionRaycastBoxes = new Map<string, ShaderSectionRaycastEntry>()
106
142
  /** Per-raycast block dedup; safe while the eye is inside at most one section aggregate AABB per call. */
@@ -146,7 +182,7 @@ export class ChunkMeshManager {
146
182
  // Create initial pool
147
183
  for (let i = 0; i < this.poolSize; i++) {
148
184
  const geometry = new THREE.BufferGeometry()
149
- const mesh = new THREE.Mesh(geometry, this.material)
185
+ const mesh = new THREE.Mesh(geometry, this.getLegacyShaderMaterial())
150
186
  mesh.visible = false
151
187
  mesh.matrixAutoUpdate = false
152
188
  mesh.name = 'pooled-section-mesh'
@@ -165,6 +201,7 @@ export class ChunkMeshManager {
165
201
  /** True when section has legacy vertices and/or GPU shader cube instances. */
166
202
  sectionHasRenderableContent (geometryData: MesherGeometryOutput): boolean {
167
203
  if (geometryData.positions.length > 0) return true
204
+ if ((geometryData.blend?.positions.length ?? 0) > 0) return true
168
205
  if (!this.isShaderCubesGpuEnabled()) return false
169
206
  return (geometryData.shaderCubes?.count ?? 0) > 0
170
207
  }
@@ -190,6 +227,292 @@ export class ChunkMeshManager {
190
227
  mat.needsUpdate = true
191
228
  }
192
229
 
230
+ syncLegacyShaderUniforms (): void {
231
+ const atlas = (this.material as THREE.MeshBasicMaterial).map ?? null
232
+ if (this.legacyShaderMaterial) {
233
+ this.legacyShaderMaterial.uniforms.u_atlas.value = atlas
234
+ this.legacyShaderMaterial.needsUpdate = true
235
+ }
236
+ if (this.globalLegacyShaderMaterial) {
237
+ this.globalLegacyShaderMaterial.uniforms.u_atlas.value = atlas
238
+ this.globalLegacyShaderMaterial.needsUpdate = true
239
+ }
240
+ if (this.globalLegacyBlendShaderMaterial) {
241
+ this.globalLegacyBlendShaderMaterial.uniforms.u_atlas.value = atlas
242
+ this.globalLegacyBlendShaderMaterial.needsUpdate = true
243
+ }
244
+ }
245
+
246
+ /** Render-time sky light cap (0–1, from time-of-day / 15). */
247
+ setSkyLevel (value: number): void {
248
+ const cube = this.cubeShaderMaterial ?? (this.isShaderCubesGpuEnabled() ? this.getCubeShaderMaterial() : null)
249
+ if (cube) setCubeSkyLevel(cube, value)
250
+ if (this.legacyShaderMaterial) setLegacySkyLevel(this.legacyShaderMaterial, value)
251
+ if (this.globalLegacyShaderMaterial) setLegacySkyLevel(this.globalLegacyShaderMaterial, value)
252
+ if (this.globalLegacyBlendShaderMaterial) setLegacySkyLevel(this.globalLegacyBlendShaderMaterial, value)
253
+ this.blockEntityLightRegistry.setSkyLevel(value)
254
+ }
255
+
256
+ /** Vanilla-like lightmap curve params (live tuning via window.setBlockLightmap). */
257
+ setBlockLightmapParams (params: BlockLightmapParams): void {
258
+ const cube = this.cubeShaderMaterial ?? (this.isShaderCubesGpuEnabled() ? this.getCubeShaderMaterial() : null)
259
+ if (cube) setCubeLightmapParams(cube, params)
260
+ if (this.legacyShaderMaterial) setLegacyLightmapParams(this.legacyShaderMaterial, params)
261
+ if (this.globalLegacyShaderMaterial) setLegacyLightmapParams(this.globalLegacyShaderMaterial, params)
262
+ if (this.globalLegacyBlendShaderMaterial) setLegacyLightmapParams(this.globalLegacyBlendShaderMaterial, params)
263
+ this.blockEntityLightRegistry.setLightmapParams(params)
264
+ }
265
+
266
+ private getLegacyShaderMaterial (): THREE.ShaderMaterial {
267
+ if (!this.legacyShaderMaterial) {
268
+ this.legacyShaderMaterial = createLegacyBlockMaterial()
269
+ this.syncLegacyShaderUniforms()
270
+ }
271
+ return this.legacyShaderMaterial
272
+ }
273
+
274
+ private getGlobalLegacyShaderMaterial (): THREE.ShaderMaterial {
275
+ if (!this.globalLegacyShaderMaterial) {
276
+ this.globalLegacyShaderMaterial = createGlobalLegacyBlockMaterial()
277
+ this.syncLegacyShaderUniforms()
278
+ }
279
+ return this.globalLegacyShaderMaterial
280
+ }
281
+
282
+ private getGlobalLegacyBuffer (): GlobalLegacyBuffer {
283
+ if (!this.globalLegacyBuffer) {
284
+ this.globalLegacyBuffer = new GlobalLegacyBuffer(
285
+ this.getGlobalLegacyShaderMaterial(),
286
+ this.scene,
287
+ )
288
+ this.globalLegacyBuffer.setRenderOrigin(this.renderOrigin)
289
+ }
290
+ return this.globalLegacyBuffer
291
+ }
292
+
293
+ private getGlobalLegacyBlendShaderMaterial (): THREE.ShaderMaterial {
294
+ if (!this.globalLegacyBlendShaderMaterial) {
295
+ this.globalLegacyBlendShaderMaterial = createGlobalLegacyBlendMaterial()
296
+ this.syncLegacyShaderUniforms()
297
+ }
298
+ return this.globalLegacyBlendShaderMaterial
299
+ }
300
+
301
+ private getGlobalLegacyBlendBuffer (): GlobalLegacyBuffer {
302
+ if (!this.globalLegacyBlendBuffer) {
303
+ this.globalLegacyBlendBuffer = new GlobalLegacyBuffer(
304
+ this.getGlobalLegacyBlendShaderMaterial(),
305
+ this.scene,
306
+ {
307
+ name: 'globalLegacyBlend',
308
+ initialCapacityQuads: 32_000,
309
+ growthIncrementQuads: 32_000,
310
+ },
311
+ )
312
+ this.globalLegacyBlendBuffer.setRenderOrigin(this.renderOrigin)
313
+ }
314
+ return this.globalLegacyBlendBuffer
315
+ }
316
+
317
+ getRenderOrigin (): Readonly<RenderOrigin> {
318
+ return this.renderOrigin
319
+ }
320
+
321
+ maybeRebase (camera: RenderOrigin): void {
322
+ const R = this.renderOrigin
323
+ if (
324
+ Math.abs(camera.x - R.x) <= ChunkMeshManager.REBASE_THRESHOLD
325
+ && Math.abs(camera.y - R.y) <= ChunkMeshManager.REBASE_THRESHOLD
326
+ && Math.abs(camera.z - R.z) <= ChunkMeshManager.REBASE_THRESHOLD
327
+ ) {
328
+ return
329
+ }
330
+
331
+ const newOrigin: RenderOrigin = {
332
+ x: Math.round(camera.x / 16) * 16,
333
+ y: Math.round(camera.y / 16) * 16,
334
+ z: Math.round(camera.z / 16) * 16,
335
+ }
336
+ const delta: RenderOrigin = {
337
+ x: newOrigin.x - R.x,
338
+ y: newOrigin.y - R.y,
339
+ z: newOrigin.z - R.z,
340
+ }
341
+
342
+ this.globalLegacyBuffer?.rebase(delta)
343
+ this.globalLegacyBlendBuffer?.rebase(delta)
344
+
345
+ for (const poolEntry of this.activeSections.values()) {
346
+ const sectionKey = poolEntry.sectionKey
347
+ if (!sectionKey) continue
348
+ const sectionObject = this.sectionObjects[sectionKey]
349
+ if (!sectionObject) continue
350
+ setupLegacySectionMatrix(
351
+ poolEntry.mesh,
352
+ sectionObject.worldX ?? 0,
353
+ sectionObject.worldY ?? 0,
354
+ sectionObject.worldZ ?? 0,
355
+ newOrigin,
356
+ )
357
+ }
358
+
359
+ this.renderOrigin = newOrigin
360
+ this.globalLegacyBuffer?.setRenderOrigin(newOrigin)
361
+ this.globalLegacyBlendBuffer?.setRenderOrigin(newOrigin)
362
+ }
363
+
364
+ /** Whether a section still holds a pooled legacy mesh (defer / invariant fallback). */
365
+ sectionUsesPooledLegacyMesh (sectionKey: string): boolean {
366
+ return this.activeSections.has(sectionKey)
367
+ }
368
+
369
+ /**
370
+ * Shared section visibility + span groups for global legacy and cube buffers.
371
+ */
372
+ updateSectionCullAndSort (camera: THREE.Camera, cameraWorldX: number, cameraWorldY: number, cameraWorldZ: number): void {
373
+ this._legacyCullProjScreen.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse)
374
+ this._legacyCullFrustum.setFromProjectionMatrix(this._legacyCullProjScreen)
375
+
376
+ const visible = this._visibleSectionSpans
377
+ visible.length = 0
378
+
379
+ for (const [sectionKey, sectionObject] of Object.entries(this.sectionObjects)) {
380
+ if (sectionObject.worldX === undefined || !sectionObject.visible) continue
381
+ const { visible: inFrustum, distSq } = sectionIntersectsFrustum(
382
+ sectionObject.worldX,
383
+ sectionObject.worldY ?? 0,
384
+ sectionObject.worldZ ?? 0,
385
+ cameraWorldX,
386
+ cameraWorldY,
387
+ cameraWorldZ,
388
+ this._legacyCullFrustum,
389
+ this._legacyCullBox,
390
+ this._legacyCullBoxMin,
391
+ this._legacyCullBoxMax,
392
+ )
393
+ if (inFrustum) {
394
+ visible.push({ key: sectionKey, distSq })
395
+ }
396
+ }
397
+
398
+ this.globalLegacyBuffer?.updateDrawSpans(visible, 'opaque')
399
+ this.globalLegacyBlendBuffer?.updateDrawSpans(visible, 'sortedBlend')
400
+
401
+ const gb = this.globalBlockBuffer
402
+ if (gb) {
403
+ const visibleSlots: Array<{ start: number, count: number }> = []
404
+ gb.forEachSectionSlot((key, slot) => {
405
+ const entry = this.shaderSectionRaycastBoxes.get(key)
406
+ if (!entry) {
407
+ return
408
+ }
409
+ const { visible: inFrustum } = sectionIntersectsFrustum(
410
+ entry.sectionCenterX,
411
+ entry.sectionCenterY,
412
+ entry.sectionCenterZ,
413
+ cameraWorldX,
414
+ cameraWorldY,
415
+ cameraWorldZ,
416
+ this._legacyCullFrustum,
417
+ this._legacyCullBox,
418
+ this._legacyCullBoxMin,
419
+ this._legacyCullBoxMax,
420
+ )
421
+ if (!inFrustum) {
422
+ return
423
+ }
424
+ const drawStart = gb.getSectionDrawStart(key)
425
+ if (drawStart !== undefined) {
426
+ visibleSlots.push({ start: drawStart, count: slot.count })
427
+ }
428
+ })
429
+ const spans = buildVisibleCubeSpans(visibleSlots, gb.getHighWatermark())
430
+ gb.setVisibleSpans(spans)
431
+ }
432
+
433
+ for (const poolEntry of this.activeSections.values()) {
434
+ const sectionKey = poolEntry.sectionKey
435
+ if (!sectionKey) continue
436
+ const sectionObject = this.sectionObjects[sectionKey]
437
+ if (!sectionObject) continue
438
+
439
+ updateLegacySectionCullState(
440
+ poolEntry.mesh,
441
+ sectionObject.worldX ?? 0,
442
+ sectionObject.worldY ?? 0,
443
+ sectionObject.worldZ ?? 0,
444
+ cameraWorldX,
445
+ cameraWorldY,
446
+ cameraWorldZ,
447
+ this._legacyCullFrustum,
448
+ this._legacyCullBox,
449
+ this._legacyCullBoxMin,
450
+ this._legacyCullBoxMax,
451
+ )
452
+ }
453
+ }
454
+
455
+ markCullDirty (): void {
456
+ this.cullDirty = true
457
+ }
458
+
459
+ /** Compare camera pose; mark cull dirty when position or rotation changed. */
460
+ updateCullDirtyFromCamera (camera: THREE.Camera, cameraWorldX: number, cameraWorldY: number, cameraWorldZ: number): void {
461
+ camera.getWorldQuaternion(this._cullViewQuat)
462
+ if (!this._cullCamInitialized) {
463
+ this._lastCullCamPos.set(cameraWorldX, cameraWorldY, cameraWorldZ)
464
+ this._lastCullCamQuat.copy(this._cullViewQuat)
465
+ this._cullCamInitialized = true
466
+ this.cullDirty = true
467
+ return
468
+ }
469
+ const posChanged =
470
+ this._lastCullCamPos.x !== cameraWorldX ||
471
+ this._lastCullCamPos.y !== cameraWorldY ||
472
+ this._lastCullCamPos.z !== cameraWorldZ
473
+ const quatChanged =
474
+ this._lastCullCamQuat.x !== this._cullViewQuat.x ||
475
+ this._lastCullCamQuat.y !== this._cullViewQuat.y ||
476
+ this._lastCullCamQuat.z !== this._cullViewQuat.z ||
477
+ this._lastCullCamQuat.w !== this._cullViewQuat.w
478
+ if (posChanged || quatChanged) {
479
+ this._lastCullCamPos.set(cameraWorldX, cameraWorldY, cameraWorldZ)
480
+ this._lastCullCamQuat.copy(this._cullViewQuat)
481
+ this.markCullDirty()
482
+ }
483
+ }
484
+
485
+ clearCullDirty (): void {
486
+ this.cullDirty = false
487
+ }
488
+
489
+ setLegacyCameraOrigin (x: number, y: number, z: number): void {
490
+ const R = this.renderOrigin
491
+ setLegacyCameraOrigin(this.getLegacyShaderMaterial(), R, x, y, z)
492
+ setLegacyCameraOrigin(this.getGlobalLegacyShaderMaterial(), R, x, y, z)
493
+ setLegacyCameraOrigin(this.getGlobalLegacyBlendShaderMaterial(), R, x, y, z)
494
+ this.globalLegacyBuffer?.setCameraOrigin(x, y, z)
495
+ this.globalLegacyBlendBuffer?.setCameraOrigin(x, y, z)
496
+
497
+ const cubeMat = this.cubeShaderMaterial
498
+ if (cubeMat) {
499
+ const { originDelta, cameraOriginFrac } = computeCameraRelativeUniforms(R, x, y, z)
500
+ const sectionOriginRel = computeSectionOriginRel(R)
501
+ const u = cubeMat.uniforms.u_originDelta
502
+ if (u?.value?.set) {
503
+ u.value.set(originDelta.x, originDelta.y, originDelta.z)
504
+ }
505
+ const uf = cubeMat.uniforms.u_cameraOriginFrac
506
+ if (uf?.value?.set) {
507
+ uf.value.set(cameraOriginFrac.x, cameraOriginFrac.y, cameraOriginFrac.z)
508
+ }
509
+ const us = cubeMat.uniforms.u_sectionOriginRel
510
+ if (us?.value?.set) {
511
+ us.value.set(sectionOriginRel.x, sectionOriginRel.y, sectionOriginRel.z)
512
+ }
513
+ }
514
+ }
515
+
193
516
  private getCubeShaderMaterial (): THREE.ShaderMaterial | null {
194
517
  if (!this.isShaderCubesGpuEnabled()) return null
195
518
  if (!this.cubeShaderMaterial) {
@@ -208,15 +531,16 @@ export class ChunkMeshManager {
208
531
  return this.globalBlockBuffer
209
532
  }
210
533
 
211
- /** Sci-fi reveal needs per-section shader meshes (or no global add) until the first wave completes. */
534
+ private shouldDeferLegacyOpaqueToPerSection (sectionKey: string): boolean {
535
+ return this.shouldDeferShaderToPerSection(sectionKey)
536
+ }
537
+
538
+ /** Sci-fi reveal keeps geometry off global buffers until the section finishes reveal. */
212
539
  private shouldDeferShaderToPerSection (sectionKey: string): boolean {
213
540
  const sciFi = this.worldRenderer.getModule<{
214
- shouldUseRevealEffect?: (key: string) => boolean
215
- isInInitialRevealCampaign?: () => boolean
541
+ shouldDeferSectionGeometry?: (key: string) => boolean
216
542
  }>('futuristicReveal')
217
- if (!sciFi) return false
218
- if (sciFi.isInInitialRevealCampaign?.()) return true
219
- return sciFi.shouldUseRevealEffect?.(sectionKey) === true
543
+ return sciFi?.shouldDeferSectionGeometry?.(sectionKey) === true
220
544
  }
221
545
 
222
546
  /**
@@ -237,6 +561,7 @@ export class ChunkMeshManager {
237
561
 
238
562
  const global = this.getGlobalBlockBuffer()
239
563
  global?.addSection(sectionKey, words, count)
564
+ this.markCullDirty()
240
565
 
241
566
  const hadShaderAsPrimary = section.mesh === (section.shaderMesh as unknown as THREE.Mesh | undefined)
242
567
  if (section.shaderMesh) {
@@ -251,6 +576,104 @@ export class ChunkMeshManager {
251
576
  }
252
577
  }
253
578
 
579
+ /**
580
+ * Move deferred per-section opaque legacy into the global buffer after reveal completes.
581
+ */
582
+ migrateDeferredLegacyToGlobal (sectionKey: string): void {
583
+ const section = this.sectionObjects[sectionKey]
584
+ if (!section) return
585
+
586
+ if (section.deferredLegacyOpaque) {
587
+ const { positions, colors, skyLights, blockLights, uvs, indices } = section.deferredLegacyOpaque
588
+ const wx = section.worldX
589
+ const wy = section.worldY
590
+ const wz = section.worldZ
591
+ if (wx !== undefined && wy !== undefined && wz !== undefined) {
592
+ this.getGlobalLegacyBuffer().addSection(
593
+ sectionKey,
594
+ { positions, colors, skyLights, blockLights, uvs, indices },
595
+ wx,
596
+ wy,
597
+ wz,
598
+ )
599
+ }
600
+ delete section.deferredLegacyOpaque
601
+ }
602
+
603
+ if (section.deferredLegacyBlend) {
604
+ const { positions, colors, skyLights, blockLights, uvs, indices } = section.deferredLegacyBlend
605
+ const wx = section.worldX
606
+ const wy = section.worldY
607
+ const wz = section.worldZ
608
+ if (wx !== undefined && wy !== undefined && wz !== undefined) {
609
+ this.getGlobalLegacyBlendBuffer().addSection(
610
+ sectionKey,
611
+ { positions, colors, skyLights, blockLights, uvs, indices },
612
+ wx,
613
+ wy,
614
+ wz,
615
+ )
616
+ }
617
+ delete section.deferredLegacyBlend
618
+ section.hasBlendMesh = false
619
+ }
620
+
621
+ if (!section.hasBlendMesh) {
622
+ const hadLegacyAsPrimary = section.mesh === this.activeSections.get(sectionKey)?.mesh
623
+ this.releasePooledMesh(sectionKey)
624
+ if (hadLegacyAsPrimary) {
625
+ section.mesh = section.shaderMesh as unknown as THREE.Mesh<THREE.BufferGeometry, THREE.Material> | undefined ?? undefined
626
+ }
627
+ }
628
+ this.markCullDirty()
629
+ }
630
+
631
+ raycastGlobalLegacySections (
632
+ raycaster: THREE.Raycaster,
633
+ origin: THREE.Vector3,
634
+ maxCenterDistance: number,
635
+ ): number | undefined {
636
+ const maxDistSq = maxCenterDistance * maxCenterDistance
637
+ const dirX = raycaster.ray.direction.x
638
+ const dirY = raycaster.ray.direction.y
639
+ const dirZ = raycaster.ray.direction.z
640
+ const far = raycaster.far
641
+ const halfExtent = LEGACY_SECTION_HALF_EXTENT + 0.01
642
+ const candidates: string[] = []
643
+ for (const [key, section] of Object.entries(this.sectionObjects)) {
644
+ if (section.worldX === undefined) continue
645
+ const dx = section.worldX - origin.x
646
+ const dy = (section.worldY ?? 0) - origin.y
647
+ const dz = (section.worldZ ?? 0) - origin.z
648
+ if (dx * dx + dy * dy + dz * dz > maxDistSq) continue
649
+ const inOpaque = this.globalLegacyBuffer?.hasSection(key) ?? false
650
+ const inBlend = this.globalLegacyBlendBuffer?.hasSection(key) ?? false
651
+ if (!inOpaque && !inBlend) continue
652
+ if (!sectionAabbIntersectsRay(
653
+ section.worldX,
654
+ section.worldY ?? 0,
655
+ section.worldZ ?? 0,
656
+ origin.x,
657
+ origin.y,
658
+ origin.z,
659
+ dirX,
660
+ dirY,
661
+ dirZ,
662
+ far,
663
+ halfExtent,
664
+ )) continue
665
+ candidates.push(key)
666
+ }
667
+
668
+ if (candidates.length === 0) return undefined
669
+
670
+ const hits: THREE.Intersection[] = []
671
+ this.globalLegacyBuffer?.raycastSections(raycaster, candidates, hits)
672
+ this.globalLegacyBlendBuffer?.raycastSections(raycaster, candidates, hits)
673
+
674
+ return hits[0]?.distance
675
+ }
676
+
254
677
  registerShaderSectionRaycastBox (
255
678
  sectionKey: string,
256
679
  words: Uint32Array,
@@ -363,8 +786,55 @@ export class ChunkMeshManager {
363
786
  /**
364
787
  * Update or create a section with new geometry data
365
788
  */
789
+ private uploadLegacyPooledMesh (
790
+ poolEntry: ChunkMeshPool,
791
+ geometryData: MesherGeometryOutput | MesherGeometryOutput['blend'],
792
+ sx: number,
793
+ sy: number,
794
+ sz: number,
795
+ ): THREE.Mesh<THREE.BufferGeometry, THREE.Material> {
796
+ const { mesh } = poolEntry
797
+ const geo = geometryData!
798
+ this.updateGeometryAttribute(mesh.geometry, 'position', geo.positions, 3)
799
+ this.updateGeometryAttribute(mesh.geometry, 'normal', geo.normals, 3)
800
+ this.updateGeometryAttribute(mesh.geometry, 'color', geo.colors, 3)
801
+ this.updateGeometryAttribute(mesh.geometry, 'a_skyLight', geo.skyLights, 1)
802
+ this.updateGeometryAttribute(mesh.geometry, 'a_blockLight', geo.blockLights, 1)
803
+ this.updateGeometryAttribute(mesh.geometry, 'uv', geo.uvs, 2)
804
+ mesh.geometry.index = new THREE.BufferAttribute(geo.indices as Uint32Array | Uint16Array, 1)
805
+ mesh.geometry.boundingBox = new THREE.Box3(
806
+ new THREE.Vector3(-8, -8, -8),
807
+ new THREE.Vector3(8, 8, 8),
808
+ )
809
+ mesh.geometry.boundingSphere = new THREE.Sphere(
810
+ new THREE.Vector3(0, 0, 0),
811
+ Math.sqrt(3 * 8 ** 2),
812
+ )
813
+ setupLegacySectionMatrix(mesh, sx, sy, sz, this.renderOrigin)
814
+ mesh.visible = false
815
+ mesh.name = 'mesh'
816
+ poolEntry.lastUsedTime = performance.now()
817
+ return mesh as THREE.Mesh<THREE.BufferGeometry, THREE.Material>
818
+ }
819
+
820
+ private acquirePooledSectionMesh (sectionKey: string): ChunkMeshPool | null {
821
+ let poolEntry = this.activeSections.get(sectionKey)
822
+ if (!poolEntry) {
823
+ poolEntry = this.acquireMesh()
824
+ if (!poolEntry) {
825
+ console.warn(`ChunkMeshManager: No available mesh in pool for section ${sectionKey}`)
826
+ return null
827
+ }
828
+ this.activeSections.set(sectionKey, poolEntry)
829
+ poolEntry.sectionKey = sectionKey
830
+ }
831
+ return poolEntry
832
+ }
833
+
366
834
  updateSection (sectionKey: string, geometryData: MesherGeometryOutput): SectionObject | null {
367
- const hasLegacy = geometryData.positions.length > 0
835
+ const hasOpaque = geometryData.positions.length > 0
836
+ const hasBlend = (geometryData.blend?.positions.length ?? 0) > 0
837
+ const hasLegacy = hasOpaque || hasBlend
368
838
  const shaderData = geometryData.shaderCubes
369
839
  const hasShader = this.isShaderCubesGpuEnabled() && (shaderData?.count ?? 0) > 0
370
840
 
@@ -376,53 +846,115 @@ export class ChunkMeshManager {
376
846
  // Remove existing section object from scene if it exists
377
847
  let sectionObject = this.sectionObjects[sectionKey]
378
848
  if (sectionObject) {
379
- this.cleanupSection(sectionKey)
849
+ this.cleanupSection(sectionKey, { forRemesh: true })
380
850
  }
381
851
 
382
- if (!hasLegacy) {
852
+ if (!hasBlend) {
383
853
  this.releasePooledMesh(sectionKey)
384
854
  }
385
855
 
386
856
  let legacyMesh: THREE.Mesh<THREE.BufferGeometry, THREE.Material> | undefined
387
- if (hasLegacy) {
388
- let poolEntry = this.activeSections.get(sectionKey)
389
- if (!poolEntry) {
390
- poolEntry = this.acquireMesh()
391
- if (!poolEntry) {
392
- console.warn(`ChunkMeshManager: No available mesh in pool for section ${sectionKey}`)
393
- return null
857
+ let deferredLegacyOpaque: LegacySectionGeometry | undefined
858
+ let deferredLegacyBlend: LegacySectionGeometry | undefined
859
+ let hasBlendMesh = false
860
+
861
+ if (hasOpaque) {
862
+ const opaqueGeo: LegacySectionGeometry = {
863
+ positions: geometryData.positions as Float32Array,
864
+ colors: geometryData.colors as Float32Array,
865
+ skyLights: geometryData.skyLights as Float32Array,
866
+ blockLights: geometryData.blockLights as Float32Array,
867
+ uvs: geometryData.uvs as Float32Array,
868
+ indices: geometryData.indices as Uint32Array | Uint16Array,
869
+ }
870
+ const deferOpaque = this.shouldDeferLegacyOpaqueToPerSection(sectionKey)
871
+ if (deferOpaque) {
872
+ deferredLegacyOpaque = {
873
+ positions: new Float32Array(opaqueGeo.positions),
874
+ colors: new Float32Array(opaqueGeo.colors),
875
+ skyLights: new Float32Array(opaqueGeo.skyLights),
876
+ blockLights: new Float32Array(opaqueGeo.blockLights),
877
+ uvs: new Float32Array(opaqueGeo.uvs),
878
+ indices: opaqueGeo.indices instanceof Uint32Array
879
+ ? new Uint32Array(opaqueGeo.indices)
880
+ : new Uint16Array(opaqueGeo.indices),
881
+ }
882
+ if (!hasBlend) {
883
+ const poolEntry = this.acquirePooledSectionMesh(sectionKey)
884
+ if (!poolEntry) return null
885
+ legacyMesh = this.uploadLegacyPooledMesh(poolEntry, geometryData, geometryData.sx, geometryData.sy, geometryData.sz)
886
+ }
887
+ } else {
888
+ const added = this.getGlobalLegacyBuffer().addSection(
889
+ sectionKey,
890
+ opaqueGeo,
891
+ geometryData.sx,
892
+ geometryData.sy,
893
+ geometryData.sz,
894
+ )
895
+ if (!added) {
896
+ const poolEntry = this.acquirePooledSectionMesh(sectionKey)
897
+ if (!poolEntry) return null
898
+ legacyMesh = this.uploadLegacyPooledMesh(poolEntry, geometryData, geometryData.sx, geometryData.sy, geometryData.sz)
394
899
  }
395
-
396
- this.activeSections.set(sectionKey, poolEntry)
397
- poolEntry.sectionKey = sectionKey
398
900
  }
901
+ }
399
902
 
400
- const { mesh } = poolEntry
401
-
402
- this.updateGeometryAttribute(mesh.geometry, 'position', geometryData.positions, 3)
403
- this.updateGeometryAttribute(mesh.geometry, 'normal', geometryData.normals, 3)
404
- this.updateGeometryAttribute(mesh.geometry, 'color', geometryData.colors, 3)
405
- this.updateGeometryAttribute(mesh.geometry, 'uv', geometryData.uvs, 2)
406
-
407
- mesh.geometry.index = new THREE.BufferAttribute(geometryData.indices as Uint32Array | Uint16Array, 1)
408
-
409
- mesh.geometry.boundingBox = new THREE.Box3(
410
- new THREE.Vector3(-8, -8, -8),
411
- new THREE.Vector3(8, 8, 8)
412
- )
413
- mesh.geometry.boundingSphere = new THREE.Sphere(
414
- new THREE.Vector3(0, 0, 0),
415
- Math.sqrt(3 * 8 ** 2)
416
- )
417
-
418
- this.worldRenderer.sceneOrigin.track(mesh, { updateMatrix: true })
419
- mesh.position.set(geometryData.sx, geometryData.sy, geometryData.sz)
420
- mesh.updateMatrix()
421
- mesh.visible = true
422
- mesh.name = 'mesh'
423
-
424
- poolEntry.lastUsedTime = performance.now()
425
- legacyMesh = mesh as THREE.Mesh<THREE.BufferGeometry, THREE.Material>
903
+ if (hasBlend && geometryData.blend) {
904
+ const blendGeo: LegacySectionGeometry = {
905
+ positions: geometryData.blend.positions as Float32Array,
906
+ colors: geometryData.blend.colors as Float32Array,
907
+ skyLights: geometryData.blend.skyLights as Float32Array,
908
+ blockLights: geometryData.blend.blockLights as Float32Array,
909
+ uvs: geometryData.blend.uvs as Float32Array,
910
+ indices: geometryData.blend.indices as Uint32Array | Uint16Array,
911
+ }
912
+ const deferBlend = this.shouldDeferLegacyOpaqueToPerSection(sectionKey)
913
+ if (deferBlend) {
914
+ deferredLegacyBlend = {
915
+ positions: new Float32Array(blendGeo.positions),
916
+ colors: new Float32Array(blendGeo.colors),
917
+ skyLights: new Float32Array(blendGeo.skyLights),
918
+ blockLights: new Float32Array(blendGeo.blockLights),
919
+ uvs: new Float32Array(blendGeo.uvs),
920
+ indices: blendGeo.indices instanceof Uint32Array
921
+ ? new Uint32Array(blendGeo.indices)
922
+ : new Uint16Array(blendGeo.indices),
923
+ }
924
+ const poolEntry = this.acquirePooledSectionMesh(sectionKey)
925
+ if (!poolEntry) return null
926
+ const blendMesh = this.uploadLegacyPooledMesh(
927
+ poolEntry,
928
+ geometryData.blend,
929
+ geometryData.sx,
930
+ geometryData.sy,
931
+ geometryData.sz,
932
+ )
933
+ legacyMesh = legacyMesh ?? blendMesh
934
+ hasBlendMesh = true
935
+ } else {
936
+ const added = this.getGlobalLegacyBlendBuffer().addSection(
937
+ sectionKey,
938
+ blendGeo,
939
+ geometryData.sx,
940
+ geometryData.sy,
941
+ geometryData.sz,
942
+ )
943
+ if (!added) {
944
+ console.warn(`ChunkMeshManager: blend invariant violation for section ${sectionKey}, using pooled mesh fallback`)
945
+ const poolEntry = this.acquirePooledSectionMesh(sectionKey)
946
+ if (!poolEntry) return null
947
+ const blendMesh = this.uploadLegacyPooledMesh(
948
+ poolEntry,
949
+ geometryData.blend,
950
+ geometryData.sx,
951
+ geometryData.sy,
952
+ geometryData.sz,
953
+ )
954
+ legacyMesh = legacyMesh ?? blendMesh
955
+ hasBlendMesh = true
956
+ }
957
+ }
426
958
  }
427
959
 
428
960
  const cubeMaterial = hasShader ? this.getCubeShaderMaterial() : null
@@ -455,6 +987,12 @@ export class ChunkMeshManager {
455
987
  sectionObject.mesh = shaderMesh as unknown as THREE.Mesh<THREE.BufferGeometry, THREE.Material>
456
988
  }
457
989
  }
990
+ if (deferredLegacyOpaque) {
991
+ sectionObject.deferredLegacyOpaque = deferredLegacyOpaque
992
+ }
993
+ if (deferredLegacyBlend) {
994
+ sectionObject.deferredLegacyBlend = deferredLegacyBlend
995
+ }
458
996
  if (hasShader && shaderData) {
459
997
  this.registerShaderSectionRaycastBox(
460
998
  sectionKey,
@@ -467,13 +1005,17 @@ export class ChunkMeshManager {
467
1005
  }
468
1006
 
469
1007
  let tilesCount = 0
470
- if (hasLegacy) {
1008
+ if (hasOpaque) {
471
1009
  tilesCount += geometryData.positions.length / 3 / 4
472
1010
  }
1011
+ if (hasBlend && geometryData.blend) {
1012
+ tilesCount += geometryData.blend.positions.length / 3 / 4
1013
+ }
473
1014
  if (hasShader && shaderData) {
474
1015
  tilesCount += shaderData.count
475
1016
  }
476
1017
  sectionObject.tilesCount = tilesCount
1018
+ sectionObject.hasBlendMesh = hasBlendMesh
477
1019
  sectionObject.blocksCount = geometryData.blocksCount
478
1020
  sectionObject.worldX = geometryData.sx
479
1021
  sectionObject.worldY = geometryData.sy
@@ -526,13 +1068,30 @@ export class ChunkMeshManager {
526
1068
  bannersContainer.name = 'banners'
527
1069
  sectionObject.bannersContainer = bannersContainer
528
1070
  sectionObject.add(bannersContainer)
529
- for (const [posKey, { isWall, rotation, blockName }] of Object.entries(geometryData.banners)) {
1071
+ for (const [posKey, bannerMeta] of Object.entries(geometryData.banners)) {
1072
+ const { isWall, rotation, blockName, blockLightNorm = 0, skyLightNorm = 1 } = bannerMeta
530
1073
  const bannerBlockEntity = this.worldRenderer.blockEntities[posKey]
531
1074
  if (!bannerBlockEntity) continue
532
1075
  const [x, y, z] = posKey.split(',')
533
1076
  const bannerTexture = getBannerTexture(this.worldRenderer, blockName, nbt.simplify(bannerBlockEntity))
534
1077
  if (!bannerTexture) continue
535
- const banner = createBannerMesh(new Vec3(+x, +y, +z), rotation, isWall, bannerTexture)
1078
+ const skyLevel = this.blockEntityLightRegistry.getSkyLevel()
1079
+ const banner = createBannerMesh(
1080
+ new Vec3(+x, +y, +z),
1081
+ rotation,
1082
+ isWall,
1083
+ bannerTexture,
1084
+ blockLightNorm,
1085
+ skyLightNorm,
1086
+ skyLevel,
1087
+ )
1088
+ if (banner.bannerMaterial) {
1089
+ this.blockEntityLightRegistry.register({
1090
+ material: banner.bannerMaterial,
1091
+ blockLightNorm,
1092
+ skyLightNorm,
1093
+ })
1094
+ }
536
1095
  const { x: bwx, y: bwy, z: bwz } = banner.position
537
1096
  this.worldRenderer.sceneOrigin.track(banner)
538
1097
  banner.position.set(bwx, bwy, bwz)
@@ -574,6 +1133,7 @@ export class ChunkMeshManager {
574
1133
  if (!list.includes(sectionKey)) list.push(sectionKey)
575
1134
  }
576
1135
 
1136
+ this.markCullDirty()
577
1137
  return sectionObject
578
1138
  }
579
1139
 
@@ -747,7 +1307,7 @@ export class ChunkMeshManager {
747
1307
  }
748
1308
  }
749
1309
 
750
- cleanupSection (sectionKey: string) {
1310
+ cleanupSection (sectionKey: string, opts?: { forRemesh?: boolean }) {
751
1311
  // Remove section object from scene
752
1312
  const sectionObject = this.sectionObjects[sectionKey]
753
1313
  if (sectionObject) {
@@ -765,15 +1325,24 @@ export class ChunkMeshManager {
765
1325
  }
766
1326
  // Cleanup banner textures before disposing
767
1327
  if (sectionObject.bannersContainer) {
768
- sectionObject.bannersContainer.traverse((child) => {
769
- if ((child as any).bannerTexture) {
770
- releaseBannerTexture((child as any).bannerTexture)
1328
+ for (const child of sectionObject.bannersContainer.children) {
1329
+ const banner = child as THREE.Group & { bannerMaterial?: THREE.MeshBasicMaterial, bannerTexture?: THREE.Texture }
1330
+ if (banner.bannerMaterial) {
1331
+ this.blockEntityLightRegistry.unregister(banner.bannerMaterial)
771
1332
  }
772
- })
1333
+ if (banner.bannerTexture) {
1334
+ releaseBannerTexture(banner.bannerTexture)
1335
+ }
1336
+ }
773
1337
  this.disposeContainer(sectionObject.bannersContainer)
774
1338
  }
775
1339
  this.globalBlockBuffer?.removeSection(sectionKey)
1340
+ this.globalLegacyBuffer?.removeSection(sectionKey)
1341
+ this.globalLegacyBlendBuffer?.removeSection(sectionKey)
776
1342
  this.unregisterShaderSectionRaycastBox(sectionKey)
1343
+ this.markCullDirty()
1344
+ delete sectionObject.deferredLegacyOpaque
1345
+ delete sectionObject.deferredLegacyBlend
777
1346
  if (sectionObject.shaderMesh) {
778
1347
  disposeShaderCubeMesh(sectionObject.shaderMesh)
779
1348
  sectionObject.shaderMesh = undefined
@@ -781,7 +1350,7 @@ export class ChunkMeshManager {
781
1350
  delete sectionObject.deferredShaderCubes
782
1351
  // Dispose signs and heads containers
783
1352
  if (sectionObject.signsContainer) {
784
- this.disposeContainer(sectionObject.signsContainer)
1353
+ this.disposeContainer(sectionObject.signsContainer, false)
785
1354
  }
786
1355
  if (sectionObject.headsContainer) {
787
1356
  this.disposeContainer(sectionObject.headsContainer)
@@ -804,6 +1373,10 @@ export class ChunkMeshManager {
804
1373
  sectionObject.boxHelper = undefined
805
1374
  }
806
1375
  delete this.sectionObjects[sectionKey]
1376
+ if (!opts?.forRemesh) {
1377
+ this.worldRenderer.getModule<{ onSectionRemoved?: (key: string) => void }>('futuristicReveal')
1378
+ ?.onSectionRemoved?.(sectionKey)
1379
+ }
807
1380
  }
808
1381
  }
809
1382
 
@@ -860,7 +1433,7 @@ export class ChunkMeshManager {
860
1433
  */
861
1434
  updateBoxHelper (sectionKey: string, showChunkBorders: boolean, chunkBoxMaterial: THREE.Material = this.chunkBoxMaterial) {
862
1435
  const sectionObject = this.sectionObjects[sectionKey]
863
- if (!sectionObject?.mesh) return
1436
+ if (!sectionObject) return
864
1437
 
865
1438
  if (showChunkBorders) {
866
1439
  if (!sectionObject.boxHelper) {
@@ -1002,6 +1575,11 @@ export class ChunkMeshManager {
1002
1575
  }
1003
1576
  }
1004
1577
 
1578
+ const legacyGlobalBytes = this.globalLegacyBuffer?.getMemoryBytes() ?? 0
1579
+ const legacyBlendGlobalBytes = this.globalLegacyBlendBuffer?.getMemoryBytes() ?? 0
1580
+ totalBytes += legacyGlobalBytes + legacyBlendGlobalBytes
1581
+ positionBytes += legacyGlobalBytes + legacyBlendGlobalBytes
1582
+
1005
1583
  for (const sectionObject of Object.values(this.sectionObjects)) {
1006
1584
  const geom = sectionObject.shaderMesh?.geometry
1007
1585
  if (!geom) continue
@@ -1094,8 +1672,18 @@ export class ChunkMeshManager {
1094
1672
  this.shaderSectionRaycastBoxes.clear()
1095
1673
  this.globalBlockBuffer?.dispose()
1096
1674
  this.globalBlockBuffer = null
1675
+ this.globalLegacyBuffer?.dispose()
1676
+ this.globalLegacyBuffer = null
1677
+ this.globalLegacyBlendBuffer?.dispose()
1678
+ this.globalLegacyBlendBuffer = null
1097
1679
  this.cubeShaderMaterial?.dispose()
1098
1680
  this.cubeShaderMaterial = null
1681
+ this.legacyShaderMaterial?.dispose()
1682
+ this.legacyShaderMaterial = null
1683
+ this.globalLegacyShaderMaterial?.dispose()
1684
+ this.globalLegacyShaderMaterial = null
1685
+ this.globalLegacyBlendShaderMaterial?.dispose()
1686
+ this.globalLegacyBlendShaderMaterial = null
1099
1687
  // Drop any pending near-first reveal state and cancel safety timers.
1100
1688
  this.pendingNearReveal.clear()
1101
1689
  for (const timer of this.nearRevealTimers.values()) clearTimeout(timer)
@@ -1109,7 +1697,7 @@ export class ChunkMeshManager {
1109
1697
  private acquireMesh (): ChunkMeshPool | undefined {
1110
1698
  if (this.bypassPooling) {
1111
1699
  const entry: ChunkMeshPool = {
1112
- mesh: new THREE.Mesh(new THREE.BufferGeometry(), this.material),
1700
+ mesh: new THREE.Mesh(new THREE.BufferGeometry(), this.getLegacyShaderMaterial()),
1113
1701
  inUse: true,
1114
1702
  lastUsedTime: performance.now()
1115
1703
  }
@@ -1160,7 +1748,7 @@ export class ChunkMeshManager {
1160
1748
  // Add new meshes to pool
1161
1749
  for (let i = currentLength; i < newSize; i++) {
1162
1750
  const geometry = new THREE.BufferGeometry()
1163
- const mesh = new THREE.Mesh(geometry, this.material)
1751
+ const mesh = new THREE.Mesh(geometry, this.getLegacyShaderMaterial())
1164
1752
  mesh.visible = false
1165
1753
  mesh.matrixAutoUpdate = false
1166
1754
  mesh.name = 'pooled-section-mesh'
@@ -1195,7 +1783,7 @@ export class ChunkMeshManager {
1195
1783
  }
1196
1784
 
1197
1785
  private clearGeometry (geometry: THREE.BufferGeometry) {
1198
- const attributes = ['position', 'normal', 'color', 'uv']
1786
+ const attributes = ['position', 'normal', 'color', 'a_skyLight', 'a_blockLight', 'uv']
1199
1787
  for (const name of attributes) {
1200
1788
  if (geometry.hasAttribute(name)) {
1201
1789
  geometry.deleteAttribute(name)
@@ -1228,8 +1816,8 @@ export class ChunkMeshManager {
1228
1816
  }
1229
1817
  }
1230
1818
 
1231
- private disposeContainer (container: THREE.Group) {
1232
- disposeObject(container, true)
1819
+ private disposeContainer (container: THREE.Group, cleanTextures = true) {
1820
+ disposeObject(container, cleanTextures)
1233
1821
  }
1234
1822
 
1235
1823
  /**
@@ -1335,8 +1923,10 @@ export class ChunkMeshManager {
1335
1923
  }
1336
1924
 
1337
1925
 
1926
+ type SignTextureCacheEntry = { tex: THREE.Texture, signature: string }
1927
+
1338
1928
  class SignHeadsRenderer {
1339
- chunkTextures = new Map<string, { [pos: string]: THREE.Texture }>()
1929
+ chunkTextures = new Map<string, { [pos: string]: SignTextureCacheEntry }>()
1340
1930
 
1341
1931
  constructor (public worldRendererThree: WorldRendererThree) {
1342
1932
  }
@@ -1344,7 +1934,7 @@ class SignHeadsRenderer {
1344
1934
  dispose () {
1345
1935
  for (const [, textures] of this.chunkTextures) {
1346
1936
  for (const key of Object.keys(textures)) {
1347
- textures[key].dispose()
1937
+ textures[key]!.tex.dispose()
1348
1938
  }
1349
1939
  }
1350
1940
  this.chunkTextures.clear()
@@ -1441,8 +2031,11 @@ class SignHeadsRenderer {
1441
2031
  this.chunkTextures.set(`${chunk[0]},${chunk[1]}`, textures)
1442
2032
  }
1443
2033
  const texturekey = `${position.x},${position.y},${position.z}`
1444
- // todo investigate bug and remove this so don't need to clean in section dirty
1445
- if (textures[texturekey]) return textures[texturekey]
2034
+ const signature = JSON.stringify(blockEntity) + '|' + isHanging + '|' + backSide
2035
+ const cached = textures[texturekey]
2036
+ if (cached && cached.signature === signature) return cached.tex
2037
+
2038
+ if (cached?.tex) cached.tex.dispose()
1446
2039
 
1447
2040
  const PrismarineChat = PrismarineChatLoader(this.worldRendererThree.version)
1448
2041
  const canvas = renderSign(blockEntity, isHanging, PrismarineChat)
@@ -1451,7 +2044,7 @@ class SignHeadsRenderer {
1451
2044
  tex.magFilter = THREE.NearestFilter
1452
2045
  tex.minFilter = THREE.NearestFilter
1453
2046
  tex.needsUpdate = true
1454
- textures[texturekey] = tex
2047
+ textures[texturekey] = { tex, signature }
1455
2048
  return tex
1456
2049
  }
1457
2050
 
@@ -1466,8 +2059,9 @@ class SignHeadsRenderer {
1466
2059
  const key = `${Math.floor(x / 16)},${Math.floor(z / 16)}`
1467
2060
  const textures = this.chunkTextures.get(key)
1468
2061
  if (!textures) return
1469
- for (const k of Object.keys(textures)) {
1470
- textures[k].dispose()
2062
+ const disposedKeys = Object.keys(textures)
2063
+ for (const k of disposedKeys) {
2064
+ textures[k]!.tex.dispose()
1471
2065
  delete textures[k]
1472
2066
  }
1473
2067
  }