minecraft-renderer 0.1.79 → 0.1.81

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 (205) hide show
  1. package/README.md +66 -26
  2. package/dist/mesher.js +36 -36
  3. package/dist/mesher.js.map +4 -4
  4. package/dist/mesherWasm.js +217 -217
  5. package/dist/minecraft-renderer.js +66 -66
  6. package/dist/minecraft-renderer.js.meta.json +1 -1
  7. package/dist/threeWorker.js +450 -450
  8. package/package.json +5 -2
  9. package/src/bundler/bundlePrepare.ts +3 -8
  10. package/src/graphicsBackend/appViewer.ts +10 -7
  11. package/src/graphicsBackend/config.ts +1 -5
  12. package/src/graphicsBackend/playerState.ts +35 -34
  13. package/src/graphicsBackend/preloadWorkers.ts +43 -41
  14. package/src/graphicsBackend/rendererDefaultOptions.ts +68 -70
  15. package/src/graphicsBackend/rendererOptionsSync.ts +24 -50
  16. package/src/graphicsBackend/types.ts +2 -2
  17. package/src/index.ts +6 -27
  18. package/src/lib/DebugGui.ts +34 -31
  19. package/src/lib/animationController.ts +9 -9
  20. package/src/lib/bakeLegacyLight.ts +1 -6
  21. package/src/lib/bindAbortableListener.test.ts +32 -12
  22. package/src/lib/bindAbortableListener.ts +14 -6
  23. package/src/lib/blockEntityLightRegistry.ts +11 -20
  24. package/src/lib/blockEntityLighting.test.ts +1 -6
  25. package/src/lib/blockEntityLighting.ts +5 -5
  26. package/src/lib/buildSharedConfig.mjs +4 -4
  27. package/src/lib/buildWorkerMcDataIndexes.test.ts +4 -4
  28. package/src/lib/buildWorkerMcDataIndexes.ts +11 -19
  29. package/src/lib/cameraBobbing.ts +1 -1
  30. package/src/lib/canvas2DOverlay.example.ts +13 -33
  31. package/src/lib/canvas2DOverlay.quickstart.ts +24 -54
  32. package/src/lib/canvas2DOverlay.ts +28 -47
  33. package/src/lib/cleanupDecorator.ts +2 -2
  34. package/src/lib/createPlayerObject.test.ts +2 -7
  35. package/src/lib/createPlayerObject.ts +18 -25
  36. package/src/lib/frameTimingCollector.ts +2 -5
  37. package/src/lib/guiRenderer.ts +57 -42
  38. package/src/lib/items.ts +15 -9
  39. package/src/lib/mesherlogReader.ts +5 -6
  40. package/src/lib/simpleUtils.ts +5 -5
  41. package/src/lib/skyLight.ts +3 -3
  42. package/src/lib/smoothSwitcher.ts +11 -18
  43. package/src/lib/ui/newStats.ts +10 -12
  44. package/src/lib/utils/proxy.ts +7 -1
  45. package/src/lib/utils/skins.ts +4 -4
  46. package/src/lib/utils.ts +1 -1
  47. package/src/lib/workerMessageSanitize.ts +5 -12
  48. package/src/lib/workerProxy.ts +52 -42
  49. package/src/lib/workerSyncOps.test.ts +58 -41
  50. package/src/lib/worldrendererCommon.reconfigure.test.ts +24 -21
  51. package/src/lib/worldrendererCommon.removeColumn.test.ts +11 -13
  52. package/src/lib/worldrendererCommon.ts +349 -211
  53. package/src/mesher-legacy/mesher.ts +9 -4
  54. package/src/mesher-legacy/test/a.ts +1 -1
  55. package/src/mesher-legacy/test/mesherTester.ts +2 -2
  56. package/src/mesher-legacy/test/playground.ts +1 -1
  57. package/src/mesher-legacy/test/run/test-js.ts +1 -1
  58. package/src/mesher-legacy/test/snapshotUtils.ts +3 -8
  59. package/src/mesher-legacy/test/test-perf.ts +1 -1
  60. package/src/mesher-legacy/test/tests.test.ts +2 -2
  61. package/src/mesher-shared/blockEntityMetadata.test.ts +7 -5
  62. package/src/mesher-shared/blockEntityMetadata.ts +28 -19
  63. package/src/mesher-shared/blockPropertiesForMeshing.ts +2 -2
  64. package/src/mesher-shared/computeHeightmap.ts +2 -4
  65. package/src/mesher-shared/exportedGeometryTypes.ts +3 -3
  66. package/src/mesher-shared/faceOcclusion.ts +324 -0
  67. package/src/mesher-shared/models.ts +80 -131
  68. package/src/mesher-shared/modelsGeometryCommon.ts +30 -15
  69. package/src/mesher-shared/shared.ts +46 -46
  70. package/src/mesher-shared/standaloneRenderer.ts +40 -44
  71. package/src/mesher-shared/tests/faceOcclusion.test.ts +179 -0
  72. package/src/mesher-shared/tests/liquidQuadInvariant.test.ts +12 -8
  73. package/src/mesher-shared/vertexShading.ts +2 -11
  74. package/src/mesher-shared/world.ts +19 -19
  75. package/src/mesher-shared/worldLighting.test.ts +2 -2
  76. package/src/performanceMonitor/PerformanceMonitor.ts +4 -13
  77. package/src/performanceMonitor/formatPerformanceFactorsDebug.ts +2 -2
  78. package/src/performanceMonitor/index.ts +1 -5
  79. package/src/performanceMonitor/types.ts +1 -1
  80. package/src/playerState/playerState.ts +55 -50
  81. package/src/playerState/types.ts +2 -8
  82. package/src/playground/allEntitiesDebug.ts +19 -16
  83. package/src/playground/baseScene.ts +29 -32
  84. package/src/playground/mobileControls.tsx +133 -129
  85. package/src/playground/playground.html +76 -73
  86. package/src/playground/playground.ts +14 -1
  87. package/src/playground/playgroundUi.tsx +100 -74
  88. package/src/playground/reactUtils.ts +3 -7
  89. package/src/playground/scenes/entities.ts +10 -7
  90. package/src/playground/scenes/frequentUpdates.ts +5 -5
  91. package/src/playground/scenes/geometryExport.ts +6 -20
  92. package/src/playground/scenes/highPosEntityUpdates.ts +2 -10
  93. package/src/playground/scenes/index.ts +1 -1
  94. package/src/playground/scenes/lightingStarfield.ts +18 -12
  95. package/src/playground/scenes/main.ts +15 -11
  96. package/src/playground/scenes/partialBlockCulling.ts +31 -0
  97. package/src/playground/scenes/railsCobweb.ts +1 -1
  98. package/src/playground/scenes/rotationIssue.ts +1 -1
  99. package/src/playground/scenes/slabsOptimization.ts +1 -2
  100. package/src/playground/scenes/transparencyIssue.ts +1 -1
  101. package/src/playground/shared.ts +2 -2
  102. package/src/resourcesManager/resourcesManager.ts +29 -54
  103. package/src/resourcesManager/resourcesManager.worker.test.ts +6 -10
  104. package/src/shims/minecraftData.ts +70 -24
  105. package/src/sign-renderer/index.html +10 -12
  106. package/src/sign-renderer/index.ts +29 -29
  107. package/src/sign-renderer/playground.ts +22 -15
  108. package/src/sign-renderer/tests.test.ts +16 -13
  109. package/src/sign-renderer/vite.config.ts +2 -2
  110. package/src/three/appShared.ts +46 -25
  111. package/src/three/bannerRenderer.ts +33 -61
  112. package/src/three/cameraCollisionBlockCache.ts +156 -0
  113. package/src/three/cameraCollisionSolidity.ts +50 -0
  114. package/src/three/cameraShake.ts +15 -6
  115. package/src/three/chunkMeshManager.ts +223 -390
  116. package/src/three/cinimaticScript.ts +26 -15
  117. package/src/three/cubeDrawSpans.ts +27 -37
  118. package/src/three/cubeMultiDraw.ts +9 -22
  119. package/src/three/documentRenderer.ts +15 -18
  120. package/src/three/entities.ts +175 -150
  121. package/src/three/entity/EntityMesh.ts +114 -108
  122. package/src/three/entity/animations.js +3 -3
  123. package/src/three/entity/armorModels.ts +15 -15
  124. package/src/three/entity/gltfAnimationUtils.ts +2 -5
  125. package/src/three/fireworks.ts +34 -32
  126. package/src/three/fireworksRenderer.ts +32 -29
  127. package/src/three/globalBlockBuffer.ts +167 -100
  128. package/src/three/globalLegacyBuffer.ts +248 -264
  129. package/src/three/graphicsBackendBase.ts +19 -25
  130. package/src/three/graphicsBackendOffThread.ts +12 -15
  131. package/src/three/hand.ts +21 -21
  132. package/src/three/handLegacy.ts +3 -13
  133. package/src/three/holdingBlock.ts +116 -79
  134. package/src/three/holdingBlockFactory.ts +1 -4
  135. package/src/three/holdingBlockItemIdentity.test.ts +25 -17
  136. package/src/three/holdingBlockItemIdentity.ts +9 -6
  137. package/src/three/holdingBlockLegacy.ts +113 -132
  138. package/src/three/itemMesh.ts +20 -29
  139. package/src/three/legacyMultiDraw.ts +10 -16
  140. package/src/three/legacySectionCull.ts +8 -14
  141. package/src/three/menuBackground/activeView.ts +1 -4
  142. package/src/three/menuBackground/assetUrl.ts +1 -1
  143. package/src/three/menuBackground/classic.ts +4 -12
  144. package/src/three/menuBackground/gpuPreference.ts +1 -3
  145. package/src/three/menuBackground/index.ts +2 -11
  146. package/src/three/menuBackground/renderer.ts +1 -5
  147. package/src/three/menuBackground/types.ts +1 -4
  148. package/src/three/menuBackground/v2.ts +196 -96
  149. package/src/three/menuBackground/v2Meta.ts +3 -3
  150. package/src/three/menuBackground/worldBlocks.ts +37 -35
  151. package/src/three/modules/blockBreakParticles.ts +59 -28
  152. package/src/three/modules/cameraBobbing.ts +3 -6
  153. package/src/three/modules/index.ts +1 -1
  154. package/src/three/modules/rain.ts +10 -23
  155. package/src/three/modules/sciFiWorldReveal.ts +11 -25
  156. package/src/three/modules/starfield.ts +7 -13
  157. package/src/three/renderSlot.ts +28 -16
  158. package/src/three/rendererModuleSystem.ts +1 -3
  159. package/src/three/sceneOrigin.ts +96 -33
  160. package/src/three/sectionRaycastAabb.ts +24 -110
  161. package/src/three/shaderCubeMesh.ts +6 -17
  162. package/src/three/shaders/cubeBlockShader.ts +85 -96
  163. package/src/three/shaders/legacyBlockShader.ts +21 -43
  164. package/src/three/shaders/textureIndexMapping.ts +89 -95
  165. package/src/three/shaders/tintPalette.ts +156 -170
  166. package/src/three/signTextureCache.ts +9 -12
  167. package/src/three/skyboxRenderer.ts +62 -70
  168. package/src/three/tests/chunkMeshManagerLegacy.test.ts +34 -64
  169. package/src/three/tests/cubeDrawSpans.test.ts +55 -25
  170. package/src/three/tests/globalBlockBuffer.test.ts +217 -0
  171. package/src/three/tests/globalLegacyBuffer.test.ts +300 -41
  172. package/src/three/tests/legacyMultiDraw.test.ts +8 -29
  173. package/src/three/tests/pr3PerFrameCpuCuts.test.ts +106 -29
  174. package/src/three/tests/signTextureCache.test.ts +4 -4
  175. package/src/three/thirdPersonVoxelRaycast.ts +120 -0
  176. package/src/three/threeJsMedia.ts +55 -72
  177. package/src/three/threeJsParticles.ts +32 -41
  178. package/src/three/threeJsSound.ts +3 -3
  179. package/src/three/threeJsUtils.ts +3 -3
  180. package/src/three/threeWorker.ts +2 -2
  181. package/src/three/waypointSprite.ts +55 -82
  182. package/src/three/waypoints.ts +20 -23
  183. package/src/three/world/cursorBlock.ts +25 -12
  184. package/src/three/world/vr.ts +11 -17
  185. package/src/three/worldGeometryExport.ts +15 -34
  186. package/src/three/worldRendererThree.ts +144 -166
  187. package/src/wasm-mesher/bridge/convertChunk.ts +3 -9
  188. package/src/wasm-mesher/bridge/render-from-wasm.ts +204 -229
  189. package/src/wasm-mesher/bridge/shaderCubeBridge.ts +26 -67
  190. package/src/wasm-mesher/tests/cameraCollisionBlockCache.test.ts +82 -0
  191. package/src/wasm-mesher/tests/cullingRegression.test.ts +307 -0
  192. package/src/wasm-mesher/tests/heightmapParity.test.ts +8 -18
  193. package/src/wasm-mesher/tests/mesherWasmConversionCache.test.ts +2 -2
  194. package/src/wasm-mesher/tests/sectionRaycastAabb.test.ts +1 -93
  195. package/src/wasm-mesher/tests/shaderCubeInstances.test.ts +103 -141
  196. package/src/wasm-mesher/tests/splitColumnWasmOutput.test.ts +14 -22
  197. package/src/wasm-mesher/tests/thirdPersonVoxelRaycast.test.ts +63 -0
  198. package/src/wasm-mesher/worker/mesherWasm.ts +150 -141
  199. package/src/wasm-mesher/worker/mesherWasmConversionCache.ts +2 -8
  200. package/src/wasm-mesher/worker/mesherWasmLightDirty.test.ts +1 -3
  201. package/src/wasm-mesher/worker/mesherWasmLightDirty.ts +1 -5
  202. package/src/wasm-mesher/worker/mesherWasmRequestTracker.ts +42 -42
  203. package/src/worldView/worldView.spiral.test.ts +1 -1
  204. package/src/worldView/worldView.ts +34 -39
  205. package/src/worldView/worldViewWorkerBridge.test.ts +4 -4
@@ -3,22 +3,31 @@ import * as THREE from 'three'
3
3
  import * as nbt from 'prismarine-nbt'
4
4
  import { Vec3 } from 'vec3'
5
5
  import { MesherGeometryOutput } from '../mesher-shared/shared'
6
- import { getShaderCubeResources, SHADER_CUBES_WORDS_PER_FACE } from '../wasm-mesher/bridge/shaderCubeBridge'
7
- import { createCubeBlockMaterial, computeSectionOriginRel, setCubeSkyLevel, setCubeShadingTheme, setCubeLightmapParams, type BlockLightmapParams } from './shaders/cubeBlockShader'
8
- import { computeCameraRelativeUniforms, createGlobalLegacyBlendMaterial, createGlobalLegacyBlockMaterial, createLegacyBlockMaterial, setLegacyCameraOrigin, setLegacySkyLevel, setLegacyLightmapParams, type RenderOrigin } from './shaders/legacyBlockShader'
9
- import { LEGACY_SECTION_HALF_EXTENT, sectionIntersectsFrustum, setupLegacySectionMatrix, updateLegacySectionCullState } from './legacySectionCull'
6
+ import { getShaderCubeResources } from '../wasm-mesher/bridge/shaderCubeBridge'
7
+ import {
8
+ createCubeBlockMaterial,
9
+ computeSectionOriginRel,
10
+ setCubeSkyLevel,
11
+ setCubeShadingTheme,
12
+ setCubeLightmapParams,
13
+ type BlockLightmapParams
14
+ } from './shaders/cubeBlockShader'
15
+ import {
16
+ computeCameraRelativeUniforms,
17
+ createGlobalLegacyBlendMaterial,
18
+ createGlobalLegacyBlockMaterial,
19
+ createLegacyBlockMaterial,
20
+ setLegacyCameraOrigin,
21
+ setLegacySkyLevel,
22
+ setLegacyLightmapParams,
23
+ type RenderOrigin
24
+ } from './shaders/legacyBlockShader'
25
+ import { sectionIntersectsFrustum, setupLegacySectionMatrix, updateLegacySectionCullState } from './legacySectionCull'
10
26
  import { createShaderCubeMesh, disposeShaderCubeMesh } from './shaderCubeMesh'
11
27
  import { GlobalBlockBuffer } from './globalBlockBuffer'
12
28
  import { buildVisibleCubeSpans } from './cubeDrawSpans'
13
29
  import { GlobalLegacyBuffer, type LegacySectionGeometry } from './globalLegacyBuffer'
14
- import {
15
- computeShaderSectionRaycastAabb,
16
- isPointInsideAabb,
17
- raycastAabb,
18
- raycastShaderBlocksAabb,
19
- sectionAabbIntersectsRay,
20
- type ShaderSectionRaycastEntry,
21
- } from './sectionRaycastAabb'
30
+ import { computeShaderSectionRaycastAabb, type ShaderSectionRaycastEntry } from './sectionRaycastAabb'
22
31
  import { getMesh } from './entity/EntityMesh'
23
32
  import type { WorldRendererThree } from './worldRendererThree'
24
33
  import { armorModel } from './entity/armorModels'
@@ -39,7 +48,7 @@ export interface SectionObject extends THREE.Group {
39
48
  /** Per-section instanced shader mesh (sci-fi reveal defer only). */
40
49
  shaderMesh?: THREE.Mesh<THREE.InstancedBufferGeometry, THREE.ShaderMaterial>
41
50
  /** Shader cube words kept for migration to global buffer after reveal. */
42
- deferredShaderCubes?: { words: Uint32Array, count: number }
51
+ deferredShaderCubes?: { words: Uint32Array; count: number }
43
52
  /** Opaque legacy geometry deferred from global buffer during sci-fi reveal. */
44
53
  deferredLegacyOpaque?: LegacySectionGeometry
45
54
  /** Blend legacy geometry deferred from global buffer during sci-fi reveal. */
@@ -139,10 +148,11 @@ export class ChunkMeshManager {
139
148
  private readonly _legacyCullBox = new THREE.Box3()
140
149
  private readonly _legacyCullBoxMin = new THREE.Vector3()
141
150
  private readonly _legacyCullBoxMax = new THREE.Vector3()
142
- private readonly _visibleSectionSpans: Array<{ key: string, distSq: number }> = []
151
+ private readonly _visibleSectionSpans: Array<{ key: string; distSq: number }> = []
143
152
  /** Sections with geometry in global legacy opaque and/or blend buffers — cull/raycast scan only these. */
144
- private readonly legacyCullSections = new Map<string, { worldX: number, worldY: number, worldZ: number }>()
153
+ private readonly legacyCullSections = new Map<string, { worldX: number; worldY: number; worldZ: number }>()
145
154
  private _lastCullFingerprint = ''
155
+ private lastBufferStateKey = ''
146
156
  /** Drives per-frame cull + span rebuild; cleared after updateSectionCullAndSort. */
147
157
  cullDirty = true
148
158
  private readonly _lastCullCamPos = new THREE.Vector3()
@@ -153,11 +163,8 @@ export class ChunkMeshManager {
153
163
  globalBlockBuffer: GlobalBlockBuffer | null = null
154
164
  globalLegacyBuffer: GlobalLegacyBuffer | null = null
155
165
  globalLegacyBlendBuffer: GlobalLegacyBuffer | null = null
156
- /** Tight world AABBs for third-person raycast; block word0 read from GlobalBlockBuffer or deferred. */
166
+ /** Tight world AABBs for shader-cube frustum culling (section centers). */
157
167
  private readonly shaderSectionRaycastBoxes = new Map<string, ShaderSectionRaycastEntry>()
158
- /** Per-raycast block dedup; safe while the eye is inside at most one section aggregate AABB per call. */
159
- private readonly blockRaycastVisitGen = new Uint16Array(4096)
160
- private blockRaycastVisitStamp = 1
161
168
 
162
169
  // Performance tracking
163
170
  private hits = 0
@@ -173,20 +180,20 @@ export class ChunkMeshManager {
173
180
  private lastPerformanceCheck = 0
174
181
  private readonly performanceCheckInterval = 2000 // Check every 2 seconds
175
182
 
176
- get performanceOverrideDistance () {
183
+ get performanceOverrideDistance() {
177
184
  return this._performanceOverrideDistance ?? 0
178
185
  }
179
- set performanceOverrideDistance (value: number | undefined) {
186
+ set performanceOverrideDistance(value: number | undefined) {
180
187
  this._performanceOverrideDistance = value
181
188
  this.updateSectionsVisibility()
182
189
  }
183
190
 
184
- constructor (
191
+ constructor(
185
192
  public worldRenderer: WorldRendererThree,
186
193
  public scene: THREE.Object3D,
187
194
  public material: THREE.Material,
188
195
  public worldHeight: number,
189
- viewDistance = 3,
196
+ viewDistance = 3
190
197
  ) {
191
198
  this.updateViewDistance(viewDistance)
192
199
  this.signHeadsRenderer = new SignHeadsRenderer(worldRenderer)
@@ -194,7 +201,7 @@ export class ChunkMeshManager {
194
201
  this.initializePool()
195
202
  }
196
203
 
197
- private initializePool () {
204
+ private initializePool() {
198
205
  // Create initial pool
199
206
  for (let i = 0; i < this.poolSize; i++) {
200
207
  const geometry = new THREE.BufferGeometry()
@@ -215,18 +222,18 @@ export class ChunkMeshManager {
215
222
  }
216
223
 
217
224
  /** True when section has legacy vertices and/or GPU shader cube instances. */
218
- sectionHasRenderableContent (geometryData: MesherGeometryOutput): boolean {
225
+ sectionHasRenderableContent(geometryData: MesherGeometryOutput): boolean {
219
226
  if (geometryData.positions.length > 0) return true
220
227
  if ((geometryData.blend?.positions.length ?? 0) > 0) return true
221
228
  if (!this.isShaderCubesGpuEnabled()) return false
222
229
  return (geometryData.shaderCubes?.count ?? 0) > 0
223
230
  }
224
231
 
225
- isShaderCubesGpuEnabled (): boolean {
232
+ isShaderCubesGpuEnabled(): boolean {
226
233
  return this.worldRenderer.shaderCubeBlocksEnabled()
227
234
  }
228
235
 
229
- syncCubeShaderUniforms (): void {
236
+ syncCubeShaderUniforms(): void {
230
237
  if (!this.isShaderCubesGpuEnabled()) return
231
238
  const mat = this.cubeShaderMaterial ?? this.getCubeShaderMaterial()
232
239
  if (!mat) return
@@ -243,7 +250,7 @@ export class ChunkMeshManager {
243
250
  mat.needsUpdate = true
244
251
  }
245
252
 
246
- syncLegacyShaderUniforms (): void {
253
+ syncLegacyShaderUniforms(): void {
247
254
  const atlas = (this.material as THREE.MeshBasicMaterial).map ?? null
248
255
  if (this.legacyShaderMaterial) {
249
256
  this.legacyShaderMaterial.uniforms.u_atlas.value = atlas
@@ -260,7 +267,7 @@ export class ChunkMeshManager {
260
267
  }
261
268
 
262
269
  /** Render-time sky light cap (0–1, from time-of-day / 15). */
263
- setSkyLevel (value: number): void {
270
+ setSkyLevel(value: number): void {
264
271
  const cube = this.cubeShaderMaterial ?? (this.isShaderCubesGpuEnabled() ? this.getCubeShaderMaterial() : null)
265
272
  if (cube) setCubeSkyLevel(cube, value)
266
273
  if (this.legacyShaderMaterial) setLegacySkyLevel(this.legacyShaderMaterial, value)
@@ -269,13 +276,13 @@ export class ChunkMeshManager {
269
276
  this.blockEntityLightRegistry.setSkyLevel(value)
270
277
  }
271
278
 
272
- setShadingTheme (theme: 'vanilla' | 'high-contrast', cardinalLight: string): void {
279
+ setShadingTheme(theme: 'vanilla' | 'high-contrast', cardinalLight: string): void {
273
280
  const cube = this.cubeShaderMaterial ?? (this.isShaderCubesGpuEnabled() ? this.getCubeShaderMaterial() : null)
274
281
  if (cube) setCubeShadingTheme(cube, theme, cardinalLight)
275
282
  }
276
283
 
277
284
  /** Vanilla-like lightmap curve params (live tuning via window.setBlockLightmap). */
278
- setBlockLightmapParams (params: BlockLightmapParams): void {
285
+ setBlockLightmapParams(params: BlockLightmapParams): void {
279
286
  const cube = this.cubeShaderMaterial ?? (this.isShaderCubesGpuEnabled() ? this.getCubeShaderMaterial() : null)
280
287
  if (cube) setCubeLightmapParams(cube, params)
281
288
  if (this.legacyShaderMaterial) setLegacyLightmapParams(this.legacyShaderMaterial, params)
@@ -284,7 +291,7 @@ export class ChunkMeshManager {
284
291
  this.blockEntityLightRegistry.setLightmapParams(params)
285
292
  }
286
293
 
287
- private getLegacyShaderMaterial (): THREE.ShaderMaterial {
294
+ private getLegacyShaderMaterial(): THREE.ShaderMaterial {
288
295
  if (!this.legacyShaderMaterial) {
289
296
  this.legacyShaderMaterial = createLegacyBlockMaterial()
290
297
  this.syncLegacyShaderUniforms()
@@ -292,7 +299,7 @@ export class ChunkMeshManager {
292
299
  return this.legacyShaderMaterial
293
300
  }
294
301
 
295
- private getGlobalLegacyShaderMaterial (): THREE.ShaderMaterial {
302
+ private getGlobalLegacyShaderMaterial(): THREE.ShaderMaterial {
296
303
  if (!this.globalLegacyShaderMaterial) {
297
304
  this.globalLegacyShaderMaterial = createGlobalLegacyBlockMaterial()
298
305
  this.syncLegacyShaderUniforms()
@@ -300,18 +307,15 @@ export class ChunkMeshManager {
300
307
  return this.globalLegacyShaderMaterial
301
308
  }
302
309
 
303
- private getGlobalLegacyBuffer (): GlobalLegacyBuffer {
310
+ private getGlobalLegacyBuffer(): GlobalLegacyBuffer {
304
311
  if (!this.globalLegacyBuffer) {
305
- this.globalLegacyBuffer = new GlobalLegacyBuffer(
306
- this.getGlobalLegacyShaderMaterial(),
307
- this.scene,
308
- )
312
+ this.globalLegacyBuffer = new GlobalLegacyBuffer(this.getGlobalLegacyShaderMaterial(), this.scene)
309
313
  this.globalLegacyBuffer.setRenderOrigin(this.renderOrigin)
310
314
  }
311
315
  return this.globalLegacyBuffer
312
316
  }
313
317
 
314
- private getGlobalLegacyBlendShaderMaterial (): THREE.ShaderMaterial {
318
+ private getGlobalLegacyBlendShaderMaterial(): THREE.ShaderMaterial {
315
319
  if (!this.globalLegacyBlendShaderMaterial) {
316
320
  this.globalLegacyBlendShaderMaterial = createGlobalLegacyBlendMaterial()
317
321
  this.syncLegacyShaderUniforms()
@@ -319,32 +323,28 @@ export class ChunkMeshManager {
319
323
  return this.globalLegacyBlendShaderMaterial
320
324
  }
321
325
 
322
- private getGlobalLegacyBlendBuffer (): GlobalLegacyBuffer {
326
+ private getGlobalLegacyBlendBuffer(): GlobalLegacyBuffer {
323
327
  if (!this.globalLegacyBlendBuffer) {
324
- this.globalLegacyBlendBuffer = new GlobalLegacyBuffer(
325
- this.getGlobalLegacyBlendShaderMaterial(),
326
- this.scene,
327
- {
328
- name: 'globalLegacyBlend',
329
- initialCapacityQuads: 32_000,
330
- growthIncrementQuads: 32_000,
331
- },
332
- )
328
+ this.globalLegacyBlendBuffer = new GlobalLegacyBuffer(this.getGlobalLegacyBlendShaderMaterial(), this.scene, {
329
+ name: 'globalLegacyBlend',
330
+ initialCapacityQuads: 32_000,
331
+ growthIncrementQuads: 32_000
332
+ })
333
333
  this.globalLegacyBlendBuffer.setRenderOrigin(this.renderOrigin)
334
334
  }
335
335
  return this.globalLegacyBlendBuffer
336
336
  }
337
337
 
338
- getRenderOrigin (): Readonly<RenderOrigin> {
338
+ getRenderOrigin(): Readonly<RenderOrigin> {
339
339
  return this.renderOrigin
340
340
  }
341
341
 
342
- maybeRebase (camera: RenderOrigin): void {
342
+ maybeRebase(camera: RenderOrigin): void {
343
343
  const R = this.renderOrigin
344
344
  if (
345
- Math.abs(camera.x - R.x) <= ChunkMeshManager.REBASE_THRESHOLD
346
- && Math.abs(camera.y - R.y) <= ChunkMeshManager.REBASE_THRESHOLD
347
- && Math.abs(camera.z - R.z) <= ChunkMeshManager.REBASE_THRESHOLD
345
+ Math.abs(camera.x - R.x) <= ChunkMeshManager.REBASE_THRESHOLD &&
346
+ Math.abs(camera.y - R.y) <= ChunkMeshManager.REBASE_THRESHOLD &&
347
+ Math.abs(camera.z - R.z) <= ChunkMeshManager.REBASE_THRESHOLD
348
348
  ) {
349
349
  return
350
350
  }
@@ -352,12 +352,12 @@ export class ChunkMeshManager {
352
352
  const newOrigin: RenderOrigin = {
353
353
  x: Math.round(camera.x / 16) * 16,
354
354
  y: Math.round(camera.y / 16) * 16,
355
- z: Math.round(camera.z / 16) * 16,
355
+ z: Math.round(camera.z / 16) * 16
356
356
  }
357
357
  const delta: RenderOrigin = {
358
358
  x: newOrigin.x - R.x,
359
359
  y: newOrigin.y - R.y,
360
- z: newOrigin.z - R.z,
360
+ z: newOrigin.z - R.z
361
361
  }
362
362
 
363
363
  this.globalLegacyBuffer?.rebase(delta)
@@ -368,13 +368,7 @@ export class ChunkMeshManager {
368
368
  if (!sectionKey) continue
369
369
  const sectionObject = this.sectionObjects[sectionKey]
370
370
  if (!sectionObject) continue
371
- setupLegacySectionMatrix(
372
- poolEntry.mesh,
373
- sectionObject.worldX ?? 0,
374
- sectionObject.worldY ?? 0,
375
- sectionObject.worldZ ?? 0,
376
- newOrigin,
377
- )
371
+ setupLegacySectionMatrix(poolEntry.mesh, sectionObject.worldX ?? 0, sectionObject.worldY ?? 0, sectionObject.worldZ ?? 0, newOrigin)
378
372
  }
379
373
 
380
374
  this.renderOrigin = newOrigin
@@ -383,15 +377,15 @@ export class ChunkMeshManager {
383
377
  }
384
378
 
385
379
  /** Whether a section still holds a pooled legacy mesh (defer / invariant fallback). */
386
- sectionUsesPooledLegacyMesh (sectionKey: string): boolean {
380
+ sectionUsesPooledLegacyMesh(sectionKey: string): boolean {
387
381
  return this.activeSections.has(sectionKey)
388
382
  }
389
383
 
390
- private registerLegacyCullSection (sectionKey: string, worldX: number, worldY: number, worldZ: number): void {
384
+ private registerLegacyCullSection(sectionKey: string, worldX: number, worldY: number, worldZ: number): void {
391
385
  this.legacyCullSections.set(sectionKey, { worldX, worldY, worldZ })
392
386
  }
393
387
 
394
- private maybeUnregisterLegacyCullSection (sectionKey: string): void {
388
+ private maybeUnregisterLegacyCullSection(sectionKey: string): void {
395
389
  const inOpaque = this.globalLegacyBuffer?.hasSection(sectionKey) ?? false
396
390
  const inBlend = this.globalLegacyBlendBuffer?.hasSection(sectionKey) ?? false
397
391
  if (!inOpaque && !inBlend) {
@@ -399,11 +393,7 @@ export class ChunkMeshManager {
399
393
  }
400
394
  }
401
395
 
402
- private updatePooledLegacyCullState (
403
- cameraWorldX: number,
404
- cameraWorldY: number,
405
- cameraWorldZ: number,
406
- ): void {
396
+ private updatePooledLegacyCullState(cameraWorldX: number, cameraWorldY: number, cameraWorldZ: number): void {
407
397
  for (const poolEntry of this.activeSections.values()) {
408
398
  const sectionKey = poolEntry.sectionKey
409
399
  if (!sectionKey) continue
@@ -421,7 +411,7 @@ export class ChunkMeshManager {
421
411
  this._legacyCullFrustum,
422
412
  this._legacyCullBox,
423
413
  this._legacyCullBoxMin,
424
- this._legacyCullBoxMax,
414
+ this._legacyCullBoxMax
425
415
  )
426
416
  }
427
417
  }
@@ -429,7 +419,7 @@ export class ChunkMeshManager {
429
419
  /**
430
420
  * Shared section visibility + span groups for global legacy and cube buffers.
431
421
  */
432
- updateSectionCullAndSort (camera: THREE.Camera, cameraWorldX: number, cameraWorldY: number, cameraWorldZ: number): void {
422
+ updateSectionCullAndSort(camera: THREE.Camera, cameraWorldX: number, cameraWorldY: number, cameraWorldZ: number): void {
433
423
  this._legacyCullProjScreen.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse)
434
424
  this._legacyCullFrustum.setFromProjectionMatrix(this._legacyCullProjScreen)
435
425
 
@@ -449,7 +439,7 @@ export class ChunkMeshManager {
449
439
  this._legacyCullFrustum,
450
440
  this._legacyCullBox,
451
441
  this._legacyCullBoxMin,
452
- this._legacyCullBoxMax,
442
+ this._legacyCullBoxMax
453
443
  )
454
444
  if (inFrustum) {
455
445
  visible.push({ key: sectionKey, distSq })
@@ -471,7 +461,7 @@ export class ChunkMeshManager {
471
461
  const blendKeys = blendVisible.map(v => v.key)
472
462
 
473
463
  const cubeVisibleKeys: string[] = []
474
- const visibleSlots: Array<{ start: number, count: number }> = []
464
+ const visibleSlots: Array<{ start: number; count: number }> = []
475
465
  if (gb) {
476
466
  gb.forEachSectionSlot((key, slot) => {
477
467
  const sectionObject = this.sectionObjects[key]
@@ -491,15 +481,16 @@ export class ChunkMeshManager {
491
481
  this._legacyCullFrustum,
492
482
  this._legacyCullBox,
493
483
  this._legacyCullBoxMin,
494
- this._legacyCullBoxMax,
484
+ this._legacyCullBoxMax
495
485
  )
496
486
  if (!inFrustum) {
497
487
  return
498
488
  }
499
489
  cubeVisibleKeys.push(key)
500
490
  const drawStart = gb.getSectionDrawStart(key)
501
- if (drawStart !== undefined) {
502
- visibleSlots.push({ start: drawStart, count: slot.count })
491
+ const drawCount = gb.getSectionDrawCount(key)
492
+ if (drawStart !== undefined && drawCount !== undefined) {
493
+ visibleSlots.push({ start: drawStart, count: drawCount })
503
494
  }
504
495
  })
505
496
  }
@@ -512,9 +503,12 @@ export class ChunkMeshManager {
512
503
  opaqueBuf?.getLayoutVersion() ?? 0,
513
504
  blendBuf?.getLayoutVersion() ?? 0,
514
505
  gb?.getLayoutVersion() ?? 0,
506
+ opaqueBuf?.getUploadEpoch() ?? 0,
507
+ blendBuf?.getUploadEpoch() ?? 0,
508
+ gb?.getUploadEpoch() ?? 0
515
509
  ].join('|')
516
510
 
517
- if (fingerprint === this._lastCullFingerprint) {
511
+ if (fingerprint === this._lastCullFingerprint && !this.hasPendingBufferWork()) {
518
512
  this.updatePooledLegacyCullState(cameraWorldX, cameraWorldY, cameraWorldZ)
519
513
  return
520
514
  }
@@ -524,19 +518,53 @@ export class ChunkMeshManager {
524
518
  blendBuf?.updateDrawSpans(visible, 'sortedBlend')
525
519
 
526
520
  if (gb) {
527
- const spans = buildVisibleCubeSpans(visibleSlots, gb.getHighWatermark())
521
+ const spans = buildVisibleCubeSpans(
522
+ visibleSlots,
523
+ gb.getHighWatermark(),
524
+ gb.canUseFullDrawShortcut(),
525
+ (start, end) => gb.isRangeFullyUploaded(start, end),
526
+ gb.getPendingDirtyRanges()
527
+ )
528
528
  gb.setVisibleSpans(spans)
529
529
  }
530
530
 
531
+ this.lastBufferStateKey = this.bufferStateKey()
531
532
  this.updatePooledLegacyCullState(cameraWorldX, cameraWorldY, cameraWorldZ)
532
533
  }
533
534
 
534
- markCullDirty (): void {
535
+ private bufferStateKey(): string {
536
+ const b = this.globalBlockBuffer
537
+ const o = this.globalLegacyBuffer
538
+ const bl = this.globalLegacyBlendBuffer
539
+ return [
540
+ o?.getLayoutVersion() ?? 0,
541
+ o?.getUploadEpoch() ?? 0,
542
+ bl?.getLayoutVersion() ?? 0,
543
+ bl?.getUploadEpoch() ?? 0,
544
+ b?.getLayoutVersion() ?? 0,
545
+ b?.getUploadEpoch() ?? 0
546
+ ].join('|')
547
+ }
548
+
549
+ /** Mark cull dirty when any buffer's layout or upload state changed since the last cull. */
550
+ markCullDirtyIfBufferStateChanged(): void {
551
+ const key = this.bufferStateKey()
552
+ if (key !== this.lastBufferStateKey) {
553
+ this.markCullDirty()
554
+ }
555
+ }
556
+
557
+ markCullDirty(): void {
535
558
  this.cullDirty = true
536
559
  }
537
560
 
561
+ hasPendingBufferWork(): boolean {
562
+ const buffers = [this.globalLegacyBuffer, this.globalLegacyBlendBuffer, this.globalBlockBuffer]
563
+ return buffers.some(b => b != null && (b.hasPendingUploads() || b.hasPendingReplace() || b.getPendingMove() != null))
564
+ }
565
+
538
566
  /** Compare camera pose; mark cull dirty when position or rotation changed. */
539
- updateCullDirtyFromCamera (camera: THREE.Camera, cameraWorldX: number, cameraWorldY: number, cameraWorldZ: number): void {
567
+ updateCullDirtyFromCamera(camera: THREE.Camera, cameraWorldX: number, cameraWorldY: number, cameraWorldZ: number): void {
540
568
  camera.getWorldQuaternion(this._cullViewQuat)
541
569
  if (!this._cullCamInitialized) {
542
570
  this._lastCullCamPos.set(cameraWorldX, cameraWorldY, cameraWorldZ)
@@ -545,10 +573,7 @@ export class ChunkMeshManager {
545
573
  this.cullDirty = true
546
574
  return
547
575
  }
548
- const posChanged =
549
- this._lastCullCamPos.x !== cameraWorldX ||
550
- this._lastCullCamPos.y !== cameraWorldY ||
551
- this._lastCullCamPos.z !== cameraWorldZ
576
+ const posChanged = this._lastCullCamPos.x !== cameraWorldX || this._lastCullCamPos.y !== cameraWorldY || this._lastCullCamPos.z !== cameraWorldZ
552
577
  const quatChanged =
553
578
  this._lastCullCamQuat.x !== this._cullViewQuat.x ||
554
579
  this._lastCullCamQuat.y !== this._cullViewQuat.y ||
@@ -561,11 +586,11 @@ export class ChunkMeshManager {
561
586
  }
562
587
  }
563
588
 
564
- clearCullDirty (): void {
589
+ clearCullDirty(): void {
565
590
  this.cullDirty = false
566
591
  }
567
592
 
568
- setLegacyCameraOrigin (x: number, y: number, z: number): void {
593
+ setLegacyCameraOrigin(x: number, y: number, z: number): void {
569
594
  const R = this.renderOrigin
570
595
  setLegacyCameraOrigin(this.getLegacyShaderMaterial(), R, x, y, z)
571
596
  setLegacyCameraOrigin(this.getGlobalLegacyShaderMaterial(), R, x, y, z)
@@ -592,7 +617,7 @@ export class ChunkMeshManager {
592
617
  }
593
618
  }
594
619
 
595
- private getCubeShaderMaterial (): THREE.ShaderMaterial | null {
620
+ private getCubeShaderMaterial(): THREE.ShaderMaterial | null {
596
621
  if (!this.isShaderCubesGpuEnabled()) return null
597
622
  if (!this.cubeShaderMaterial) {
598
623
  this.cubeShaderMaterial = createCubeBlockMaterial()
@@ -603,7 +628,7 @@ export class ChunkMeshManager {
603
628
  return this.cubeShaderMaterial
604
629
  }
605
630
 
606
- private getGlobalBlockBuffer (): GlobalBlockBuffer | null {
631
+ private getGlobalBlockBuffer(): GlobalBlockBuffer | null {
607
632
  const mat = this.getCubeShaderMaterial()
608
633
  if (!mat) return null
609
634
  if (!this.globalBlockBuffer) {
@@ -612,12 +637,12 @@ export class ChunkMeshManager {
612
637
  return this.globalBlockBuffer
613
638
  }
614
639
 
615
- private shouldDeferLegacyOpaqueToPerSection (sectionKey: string): boolean {
640
+ private shouldDeferLegacyOpaqueToPerSection(sectionKey: string): boolean {
616
641
  return this.shouldDeferShaderToPerSection(sectionKey)
617
642
  }
618
643
 
619
644
  /** Sci-fi reveal keeps geometry off global buffers until the section finishes reveal. */
620
- private shouldDeferShaderToPerSection (sectionKey: string): boolean {
645
+ private shouldDeferShaderToPerSection(sectionKey: string): boolean {
621
646
  const sciFi = this.worldRenderer.getModule<{
622
647
  shouldDeferSectionGeometry?: (key: string) => boolean
623
648
  }>('futuristicReveal')
@@ -627,7 +652,7 @@ export class ChunkMeshManager {
627
652
  /**
628
653
  * Move deferred per-section shader cubes into the global buffer after reveal completes.
629
654
  */
630
- migrateDeferredShaderToGlobal (sectionKey: string): void {
655
+ migrateDeferredShaderToGlobal(sectionKey: string): void {
631
656
  const section = this.sectionObjects[sectionKey]
632
657
  if (!section?.deferredShaderCubes) return
633
658
 
@@ -660,7 +685,7 @@ export class ChunkMeshManager {
660
685
  /**
661
686
  * Move deferred per-section opaque legacy into the global buffer after reveal completes.
662
687
  */
663
- migrateDeferredLegacyToGlobal (sectionKey: string): void {
688
+ migrateDeferredLegacyToGlobal(sectionKey: string): void {
664
689
  const section = this.sectionObjects[sectionKey]
665
690
  if (!section) return
666
691
 
@@ -670,13 +695,7 @@ export class ChunkMeshManager {
670
695
  const wy = section.worldY
671
696
  const wz = section.worldZ
672
697
  if (wx !== undefined && wy !== undefined && wz !== undefined) {
673
- const added = this.getGlobalLegacyBuffer().addSection(
674
- sectionKey,
675
- { positions, colors, skyLights, blockLights, uvs, indices },
676
- wx,
677
- wy,
678
- wz,
679
- )
698
+ const added = this.getGlobalLegacyBuffer().addSection(sectionKey, { positions, colors, skyLights, blockLights, uvs, indices }, wx, wy, wz)
680
699
  if (added) {
681
700
  this.registerLegacyCullSection(sectionKey, wx, wy, wz)
682
701
  }
@@ -690,13 +709,7 @@ export class ChunkMeshManager {
690
709
  const wy = section.worldY
691
710
  const wz = section.worldZ
692
711
  if (wx !== undefined && wy !== undefined && wz !== undefined) {
693
- const added = this.getGlobalLegacyBlendBuffer().addSection(
694
- sectionKey,
695
- { positions, colors, skyLights, blockLights, uvs, indices },
696
- wx,
697
- wy,
698
- wz,
699
- )
712
+ const added = this.getGlobalLegacyBlendBuffer().addSection(sectionKey, { positions, colors, skyLights, blockLights, uvs, indices }, wx, wy, wz)
700
713
  if (added) {
701
714
  this.registerLegacyCullSection(sectionKey, wx, wy, wz)
702
715
  }
@@ -709,63 +722,19 @@ export class ChunkMeshManager {
709
722
  const hadLegacyAsPrimary = section.mesh === this.activeSections.get(sectionKey)?.mesh
710
723
  this.releasePooledMesh(sectionKey)
711
724
  if (hadLegacyAsPrimary) {
712
- section.mesh = section.shaderMesh as unknown as THREE.Mesh<THREE.BufferGeometry, THREE.Material> | undefined ?? undefined
725
+ section.mesh = (section.shaderMesh as unknown as THREE.Mesh<THREE.BufferGeometry, THREE.Material> | undefined) ?? undefined
713
726
  }
714
727
  }
715
728
  this.markCullDirty()
716
729
  }
717
730
 
718
- raycastGlobalLegacySections (
719
- raycaster: THREE.Raycaster,
720
- origin: THREE.Vector3,
721
- maxCenterDistance: number,
722
- ): number | undefined {
723
- const maxDistSq = maxCenterDistance * maxCenterDistance
724
- const dirX = raycaster.ray.direction.x
725
- const dirY = raycaster.ray.direction.y
726
- const dirZ = raycaster.ray.direction.z
727
- const far = raycaster.far
728
- const halfExtent = LEGACY_SECTION_HALF_EXTENT + 0.01
729
- const candidates: string[] = []
730
- for (const [key] of this.legacyCullSections) {
731
- const section = this.sectionObjects[key]
732
- if (!section || section.worldX === undefined) continue
733
- const dx = section.worldX - origin.x
734
- const dy = (section.worldY ?? 0) - origin.y
735
- const dz = (section.worldZ ?? 0) - origin.z
736
- if (dx * dx + dy * dy + dz * dz > maxDistSq) continue
737
- if (!sectionAabbIntersectsRay(
738
- section.worldX,
739
- section.worldY ?? 0,
740
- section.worldZ ?? 0,
741
- origin.x,
742
- origin.y,
743
- origin.z,
744
- dirX,
745
- dirY,
746
- dirZ,
747
- far,
748
- halfExtent,
749
- )) continue
750
- candidates.push(key)
751
- }
752
-
753
- if (candidates.length === 0) return undefined
754
-
755
- const hits: THREE.Intersection[] = []
756
- this.globalLegacyBuffer?.raycastSections(raycaster, candidates, hits)
757
- this.globalLegacyBlendBuffer?.raycastSections(raycaster, candidates, hits)
758
-
759
- return hits[0]?.distance
760
- }
761
-
762
- registerShaderSectionRaycastBox (
731
+ registerShaderSectionRaycastBox(
763
732
  sectionKey: string,
764
733
  words: Uint32Array,
765
734
  faceCount: number,
766
735
  sectionCenterX: number,
767
736
  sectionCenterY: number,
768
- sectionCenterZ: number,
737
+ sectionCenterZ: number
769
738
  ): void {
770
739
  const box = computeShaderSectionRaycastAabb(words, faceCount, sectionCenterX, sectionCenterY, sectionCenterZ)
771
740
  if (box) {
@@ -773,110 +742,26 @@ export class ChunkMeshManager {
773
742
  box,
774
743
  sectionCenterX,
775
744
  sectionCenterY,
776
- sectionCenterZ,
745
+ sectionCenterZ
777
746
  })
778
747
  } else {
779
748
  this.shaderSectionRaycastBoxes.delete(sectionKey)
780
749
  }
781
750
  }
782
751
 
783
- unregisterShaderSectionRaycastBox (sectionKey: string): void {
752
+ unregisterShaderSectionRaycastBox(sectionKey: string): void {
784
753
  this.shaderSectionRaycastBoxes.delete(sectionKey)
785
754
  }
786
755
 
787
- /** Closest hit against registered shader-cube AABBs (world-space ray). */
788
- raycastShaderSectionAABBs (
789
- originWorld: THREE.Vector3,
790
- direction: THREE.Vector3,
791
- maxDist: number,
792
- maxCenterDistance = 80,
793
- ): number | undefined {
794
- const ox = originWorld.x
795
- const oy = originWorld.y
796
- const oz = originWorld.z
797
- const dx = direction.x
798
- const dy = direction.y
799
- const dz = direction.z
800
- const maxCenterDistSq = maxCenterDistance * maxCenterDistance
801
-
802
- let closest = maxDist
803
- let found = false
804
-
805
- this.blockRaycastVisitStamp++
806
- if (this.blockRaycastVisitStamp >= 65535) {
807
- this.blockRaycastVisitGen.fill(0)
808
- this.blockRaycastVisitStamp = 1
809
- }
810
-
811
- for (const [key, entry] of this.shaderSectionRaycastBoxes) {
812
- const section = this.sectionObjects[key]
813
- if (section && !section.visible) continue
814
-
815
- const { box } = entry
816
- const dcx = box.cx - ox
817
- const dcy = box.cy - oy
818
- const dcz = box.cz - oz
819
- if (dcx * dcx + dcy * dcy + dcz * dcz > maxCenterDistSq) continue
820
-
821
- let t = raycastAabb(
822
- ox, oy, oz, dx, dy, dz,
823
- box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ,
824
- closest,
825
- )
826
- if (t === undefined && isPointInsideAabb(ox, oy, oz, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ)) {
827
- const gb = this.globalBlockBuffer
828
- const slot = gb?.getSectionSlot(key)
829
- if (gb && slot) {
830
- t = raycastShaderBlocksAabb(
831
- gb.getW0(),
832
- slot.start,
833
- slot.count,
834
- 1,
835
- entry.sectionCenterX,
836
- entry.sectionCenterY,
837
- entry.sectionCenterZ,
838
- ox, oy, oz, dx, dy, dz,
839
- closest,
840
- this.blockRaycastVisitGen,
841
- this.blockRaycastVisitStamp,
842
- )
843
- } else {
844
- const def = this.sectionObjects[key]?.deferredShaderCubes
845
- if (def) {
846
- t = raycastShaderBlocksAabb(
847
- def.words,
848
- 0,
849
- def.count,
850
- SHADER_CUBES_WORDS_PER_FACE,
851
- entry.sectionCenterX,
852
- entry.sectionCenterY,
853
- entry.sectionCenterZ,
854
- ox, oy, oz, dx, dy, dz,
855
- closest,
856
- this.blockRaycastVisitGen,
857
- this.blockRaycastVisitStamp,
858
- )
859
- }
860
- }
861
- }
862
- if (t !== undefined && t < closest) {
863
- closest = t
864
- found = true
865
- }
866
- }
867
-
868
- return found ? closest : undefined
869
- }
870
-
871
756
  /**
872
757
  * Update or create a section with new geometry data
873
758
  */
874
- private uploadLegacyPooledMesh (
759
+ private uploadLegacyPooledMesh(
875
760
  poolEntry: ChunkMeshPool,
876
761
  geometryData: MesherGeometryOutput | MesherGeometryOutput['blend'],
877
762
  sx: number,
878
763
  sy: number,
879
- sz: number,
764
+ sz: number
880
765
  ): THREE.Mesh<THREE.BufferGeometry, THREE.Material> {
881
766
  const { mesh } = poolEntry
882
767
  const geo = geometryData!
@@ -887,14 +772,8 @@ export class ChunkMeshManager {
887
772
  this.updateGeometryAttribute(mesh.geometry, 'a_blockLight', geo.blockLights, 1)
888
773
  this.updateGeometryAttribute(mesh.geometry, 'uv', geo.uvs, 2)
889
774
  mesh.geometry.index = new THREE.BufferAttribute(geo.indices as Uint32Array | Uint16Array, 1)
890
- mesh.geometry.boundingBox = new THREE.Box3(
891
- new THREE.Vector3(-8, -8, -8),
892
- new THREE.Vector3(8, 8, 8),
893
- )
894
- mesh.geometry.boundingSphere = new THREE.Sphere(
895
- new THREE.Vector3(0, 0, 0),
896
- Math.sqrt(3 * 8 ** 2),
897
- )
775
+ mesh.geometry.boundingBox = new THREE.Box3(new THREE.Vector3(-8, -8, -8), new THREE.Vector3(8, 8, 8))
776
+ mesh.geometry.boundingSphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0), Math.sqrt(3 * 8 ** 2))
898
777
  setupLegacySectionMatrix(mesh, sx, sy, sz, this.renderOrigin)
899
778
  mesh.visible = false
900
779
  mesh.name = 'mesh'
@@ -902,7 +781,7 @@ export class ChunkMeshManager {
902
781
  return mesh as THREE.Mesh<THREE.BufferGeometry, THREE.Material>
903
782
  }
904
783
 
905
- private acquirePooledSectionMesh (sectionKey: string): ChunkMeshPool | null {
784
+ private acquirePooledSectionMesh(sectionKey: string): ChunkMeshPool | null {
906
785
  let poolEntry = this.activeSections.get(sectionKey)
907
786
  if (!poolEntry) {
908
787
  poolEntry = this.acquireMesh()
@@ -916,12 +795,18 @@ export class ChunkMeshManager {
916
795
  return poolEntry
917
796
  }
918
797
 
919
- updateSection (sectionKey: string, geometryData: MesherGeometryOutput): SectionObject | null {
798
+ updateSection(sectionKey: string, geometryData: MesherGeometryOutput): SectionObject | null {
920
799
  const hasOpaque = geometryData.positions.length > 0
921
800
  const hasBlend = (geometryData.blend?.positions.length ?? 0) > 0
922
801
  const hasLegacy = hasOpaque || hasBlend
923
802
  const shaderData = geometryData.shaderCubes
924
803
  const hasShader = this.isShaderCubesGpuEnabled() && (shaderData?.count ?? 0) > 0
804
+ const deferOpaque = hasOpaque && this.shouldDeferLegacyOpaqueToPerSection(sectionKey)
805
+ const deferBlend = hasBlend && this.shouldDeferLegacyOpaqueToPerSection(sectionKey)
806
+ const deferShader = hasShader && this.shouldDeferShaderToPerSection(sectionKey)
807
+ const willAddOpaqueGlobal = hasOpaque && !deferOpaque
808
+ const willAddBlendGlobal = hasBlend && !deferBlend
809
+ const willAddCubeGlobal = hasShader && !!shaderData && !deferShader
925
810
 
926
811
  if (!hasLegacy && !hasShader) {
927
812
  this.releaseSection(sectionKey)
@@ -930,10 +815,19 @@ export class ChunkMeshManager {
930
815
 
931
816
  // Remove existing section object from scene if it exists
932
817
  let sectionObject = this.sectionObjects[sectionKey]
818
+ const wasRemesh = sectionObject != null
933
819
  if (sectionObject) {
934
820
  this.cleanupSection(sectionKey, { forRemesh: true })
935
821
  }
936
822
 
823
+ if (wasRemesh) {
824
+ if (!willAddCubeGlobal) this.globalBlockBuffer?.removeSection(sectionKey)
825
+ if (!willAddOpaqueGlobal) this.globalLegacyBuffer?.removeSection(sectionKey)
826
+ if (!willAddBlendGlobal) this.globalLegacyBlendBuffer?.removeSection(sectionKey)
827
+ if (!(hasShader && shaderData)) this.unregisterShaderSectionRaycastBox(sectionKey)
828
+ if (!willAddOpaqueGlobal && !willAddBlendGlobal) this.maybeUnregisterLegacyCullSection(sectionKey)
829
+ }
830
+
937
831
  if (!hasBlend) {
938
832
  this.releasePooledMesh(sectionKey)
939
833
  }
@@ -950,9 +844,8 @@ export class ChunkMeshManager {
950
844
  skyLights: geometryData.skyLights as Float32Array,
951
845
  blockLights: geometryData.blockLights as Float32Array,
952
846
  uvs: geometryData.uvs as Float32Array,
953
- indices: geometryData.indices as Uint32Array | Uint16Array,
847
+ indices: geometryData.indices as Uint32Array | Uint16Array
954
848
  }
955
- const deferOpaque = this.shouldDeferLegacyOpaqueToPerSection(sectionKey)
956
849
  if (deferOpaque) {
957
850
  deferredLegacyOpaque = {
958
851
  positions: new Float32Array(opaqueGeo.positions),
@@ -960,9 +853,7 @@ export class ChunkMeshManager {
960
853
  skyLights: new Float32Array(opaqueGeo.skyLights),
961
854
  blockLights: new Float32Array(opaqueGeo.blockLights),
962
855
  uvs: new Float32Array(opaqueGeo.uvs),
963
- indices: opaqueGeo.indices instanceof Uint32Array
964
- ? new Uint32Array(opaqueGeo.indices)
965
- : new Uint16Array(opaqueGeo.indices),
856
+ indices: opaqueGeo.indices instanceof Uint32Array ? new Uint32Array(opaqueGeo.indices) : new Uint16Array(opaqueGeo.indices)
966
857
  }
967
858
  if (!hasBlend) {
968
859
  const poolEntry = this.acquirePooledSectionMesh(sectionKey)
@@ -970,13 +861,7 @@ export class ChunkMeshManager {
970
861
  legacyMesh = this.uploadLegacyPooledMesh(poolEntry, geometryData, geometryData.sx, geometryData.sy, geometryData.sz)
971
862
  }
972
863
  } else {
973
- const added = this.getGlobalLegacyBuffer().addSection(
974
- sectionKey,
975
- opaqueGeo,
976
- geometryData.sx,
977
- geometryData.sy,
978
- geometryData.sz,
979
- )
864
+ const added = this.getGlobalLegacyBuffer().addSection(sectionKey, opaqueGeo, geometryData.sx, geometryData.sy, geometryData.sz)
980
865
  if (added) {
981
866
  this.registerLegacyCullSection(sectionKey, geometryData.sx, geometryData.sy, geometryData.sz)
982
867
  } else {
@@ -994,9 +879,8 @@ export class ChunkMeshManager {
994
879
  skyLights: geometryData.blend.skyLights as Float32Array,
995
880
  blockLights: geometryData.blend.blockLights as Float32Array,
996
881
  uvs: geometryData.blend.uvs as Float32Array,
997
- indices: geometryData.blend.indices as Uint32Array | Uint16Array,
882
+ indices: geometryData.blend.indices as Uint32Array | Uint16Array
998
883
  }
999
- const deferBlend = this.shouldDeferLegacyOpaqueToPerSection(sectionKey)
1000
884
  if (deferBlend) {
1001
885
  deferredLegacyBlend = {
1002
886
  positions: new Float32Array(blendGeo.positions),
@@ -1004,42 +888,22 @@ export class ChunkMeshManager {
1004
888
  skyLights: new Float32Array(blendGeo.skyLights),
1005
889
  blockLights: new Float32Array(blendGeo.blockLights),
1006
890
  uvs: new Float32Array(blendGeo.uvs),
1007
- indices: blendGeo.indices instanceof Uint32Array
1008
- ? new Uint32Array(blendGeo.indices)
1009
- : new Uint16Array(blendGeo.indices),
891
+ indices: blendGeo.indices instanceof Uint32Array ? new Uint32Array(blendGeo.indices) : new Uint16Array(blendGeo.indices)
1010
892
  }
1011
893
  const poolEntry = this.acquirePooledSectionMesh(sectionKey)
1012
894
  if (!poolEntry) return null
1013
- const blendMesh = this.uploadLegacyPooledMesh(
1014
- poolEntry,
1015
- geometryData.blend,
1016
- geometryData.sx,
1017
- geometryData.sy,
1018
- geometryData.sz,
1019
- )
895
+ const blendMesh = this.uploadLegacyPooledMesh(poolEntry, geometryData.blend, geometryData.sx, geometryData.sy, geometryData.sz)
1020
896
  legacyMesh = legacyMesh ?? blendMesh
1021
897
  hasBlendMesh = true
1022
898
  } else {
1023
- const added = this.getGlobalLegacyBlendBuffer().addSection(
1024
- sectionKey,
1025
- blendGeo,
1026
- geometryData.sx,
1027
- geometryData.sy,
1028
- geometryData.sz,
1029
- )
899
+ const added = this.getGlobalLegacyBlendBuffer().addSection(sectionKey, blendGeo, geometryData.sx, geometryData.sy, geometryData.sz)
1030
900
  if (added) {
1031
901
  this.registerLegacyCullSection(sectionKey, geometryData.sx, geometryData.sy, geometryData.sz)
1032
902
  } else {
1033
903
  console.warn(`ChunkMeshManager: blend invariant violation for section ${sectionKey}, using pooled mesh fallback`)
1034
904
  const poolEntry = this.acquirePooledSectionMesh(sectionKey)
1035
905
  if (!poolEntry) return null
1036
- const blendMesh = this.uploadLegacyPooledMesh(
1037
- poolEntry,
1038
- geometryData.blend,
1039
- geometryData.sx,
1040
- geometryData.sy,
1041
- geometryData.sz,
1042
- )
906
+ const blendMesh = this.uploadLegacyPooledMesh(poolEntry, geometryData.blend, geometryData.sx, geometryData.sy, geometryData.sz)
1043
907
  legacyMesh = legacyMesh ?? blendMesh
1044
908
  hasBlendMesh = true
1045
909
  }
@@ -1048,7 +912,6 @@ export class ChunkMeshManager {
1048
912
 
1049
913
  const cubeMaterial = hasShader ? this.getCubeShaderMaterial() : null
1050
914
  let shaderMesh: THREE.Mesh<THREE.InstancedBufferGeometry, THREE.ShaderMaterial> | undefined
1051
- const deferShader = hasShader && this.shouldDeferShaderToPerSection(sectionKey)
1052
915
  if (hasShader && shaderData) {
1053
916
  if (deferShader && cubeMaterial) {
1054
917
  shaderMesh = createShaderCubeMesh(shaderData, cubeMaterial)
@@ -1069,7 +932,7 @@ export class ChunkMeshManager {
1069
932
  if (shaderData) {
1070
933
  sectionObject.deferredShaderCubes = {
1071
934
  words: shaderData.words,
1072
- count: shaderData.count,
935
+ count: shaderData.count
1073
936
  }
1074
937
  }
1075
938
  if (!sectionObject.mesh) {
@@ -1083,14 +946,7 @@ export class ChunkMeshManager {
1083
946
  sectionObject.deferredLegacyBlend = deferredLegacyBlend
1084
947
  }
1085
948
  if (hasShader && shaderData) {
1086
- this.registerShaderSectionRaycastBox(
1087
- sectionKey,
1088
- shaderData.words,
1089
- shaderData.count,
1090
- geometryData.sx,
1091
- geometryData.sy,
1092
- geometryData.sz,
1093
- )
949
+ this.registerShaderSectionRaycastBox(sectionKey, shaderData.words, shaderData.count, geometryData.sx, geometryData.sy, geometryData.sz)
1094
950
  }
1095
951
 
1096
952
  let tilesCount = 0
@@ -1165,20 +1021,12 @@ export class ChunkMeshManager {
1165
1021
  const bannerTexture = getBannerTexture(this.worldRenderer, blockName, nbt.simplify(bannerBlockEntity))
1166
1022
  if (!bannerTexture) continue
1167
1023
  const skyLevel = this.blockEntityLightRegistry.getSkyLevel()
1168
- const banner = createBannerMesh(
1169
- new Vec3(+x, +y, +z),
1170
- rotation,
1171
- isWall,
1172
- bannerTexture,
1173
- blockLightNorm,
1174
- skyLightNorm,
1175
- skyLevel,
1176
- )
1024
+ const banner = createBannerMesh(new Vec3(+x, +y, +z), rotation, isWall, bannerTexture, blockLightNorm, skyLightNorm, skyLevel)
1177
1025
  if (banner.bannerMaterial) {
1178
1026
  this.blockEntityLightRegistry.register({
1179
1027
  material: banner.bannerMaterial,
1180
1028
  blockLightNorm,
1181
- skyLightNorm,
1029
+ skyLightNorm
1182
1030
  })
1183
1031
  }
1184
1032
  const { x: bwx, y: bwy, z: bwz } = banner.position
@@ -1212,8 +1060,7 @@ export class ChunkMeshManager {
1212
1060
  // setting so the near-first reveal gate has sections to hold.
1213
1061
  const chunkCoords = sectionKey.split(',')
1214
1062
  const chunkKey = `${chunkCoords[0]},${chunkCoords[2]}`
1215
- const renderByChunks = !!this.worldRenderer.displayOptions
1216
- ?.inWorldRenderingConfig?._renderByChunks
1063
+ const renderByChunks = !!this.worldRenderer.displayOptions?.inWorldRenderingConfig?._renderByChunks
1217
1064
  const forceBatchForWasm = !!this.worldRenderer.worldRendererConfig?.wasmMesher
1218
1065
  if ((renderByChunks || forceBatchForWasm) && !this.worldRenderer.finishedChunks[chunkKey]) {
1219
1066
  sectionObject.visible = false
@@ -1234,7 +1081,7 @@ export class ChunkMeshManager {
1234
1081
  * reveal is deferred (parked in `pendingNearReveal`) and re-checked on
1235
1082
  * the next chunkFinished / player-move / grace-expiry.
1236
1083
  */
1237
- finishChunkDisplay (chunkKey: string): void {
1084
+ finishChunkDisplay(chunkKey: string): void {
1238
1085
  const sectionKeys = this.waitingChunksToDisplay[chunkKey]
1239
1086
  if (!sectionKeys) {
1240
1087
  // No held sections (empty column / non-batched path) — but the
@@ -1253,7 +1100,7 @@ export class ChunkMeshManager {
1253
1100
  this.tryRevealPending()
1254
1101
  }
1255
1102
 
1256
- private flushChunkDisplay (chunkKey: string): void {
1103
+ private flushChunkDisplay(chunkKey: string): void {
1257
1104
  const sectionKeys = this.waitingChunksToDisplay[chunkKey]
1258
1105
  this.pendingNearReveal.delete(chunkKey)
1259
1106
  this.clearNearRevealTimer(chunkKey)
@@ -1273,7 +1120,7 @@ export class ChunkMeshManager {
1273
1120
  // Single pass is enough — pending entries are already finished, so flushing
1274
1121
  // one cannot un-block another via this code path (cascading happens via
1275
1122
  // chunkFinished events and per-pending grace timers).
1276
- tryRevealPending (): void {
1123
+ tryRevealPending(): void {
1277
1124
  if (this.pendingNearReveal.size === 0) return
1278
1125
  const now = Date.now()
1279
1126
  for (const [chunkKey, enqueuedAt] of [...this.pendingNearReveal]) {
@@ -1285,7 +1132,7 @@ export class ChunkMeshManager {
1285
1132
 
1286
1133
  // Drop gate state for an unloaded column and re-evaluate any farther
1287
1134
  // chunks that may have been blocked by it.
1288
- onChunkRemovedFromGate (chunkKey: string): void {
1135
+ onChunkRemovedFromGate(chunkKey: string): void {
1289
1136
  this.pendingNearReveal.delete(chunkKey)
1290
1137
  this.clearNearRevealTimer(chunkKey)
1291
1138
  this.clearExpectedGraceTimer(chunkKey)
@@ -1293,7 +1140,7 @@ export class ChunkMeshManager {
1293
1140
  this.tryRevealPending()
1294
1141
  }
1295
1142
 
1296
- private isWasmGateActive (): boolean {
1143
+ private isWasmGateActive(): boolean {
1297
1144
  return !!this.worldRenderer.worldRendererConfig?.wasmMesher
1298
1145
  }
1299
1146
 
@@ -1307,7 +1154,7 @@ export class ChunkMeshManager {
1307
1154
  * - After grace: only actually-loaded-but-not-finished columns block,
1308
1155
  * so a never-arriving column does not freeze the view.
1309
1156
  */
1310
- private isBlockedByNearer (chunkKey: string, ageMs: number): boolean {
1157
+ private isBlockedByNearer(chunkKey: string, ageMs: number): boolean {
1311
1158
  const viewer = this.worldRenderer.viewerChunkPosition
1312
1159
  if (!viewer) return false
1313
1160
  const ownParts = chunkKey.split(',')
@@ -1354,7 +1201,7 @@ export class ChunkMeshManager {
1354
1201
  return false
1355
1202
  }
1356
1203
 
1357
- private armNearRevealTimer (chunkKey: string): void {
1204
+ private armNearRevealTimer(chunkKey: string): void {
1358
1205
  if (this.nearRevealTimers.has(chunkKey)) return
1359
1206
  const timer = setTimeout(() => {
1360
1207
  this.nearRevealTimers.delete(chunkKey)
@@ -1366,7 +1213,7 @@ export class ChunkMeshManager {
1366
1213
  this.nearRevealTimers.set(chunkKey, timer)
1367
1214
  }
1368
1215
 
1369
- private clearNearRevealTimer (chunkKey: string): void {
1216
+ private clearNearRevealTimer(chunkKey: string): void {
1370
1217
  const timer = this.nearRevealTimers.get(chunkKey)
1371
1218
  if (timer) {
1372
1219
  clearTimeout(timer)
@@ -1379,7 +1226,7 @@ export class ChunkMeshManager {
1379
1226
  * "expected but never arrived" positions stop blocking promptly,
1380
1227
  * without waiting for the next chunkFinished / player-move event.
1381
1228
  */
1382
- private armExpectedGraceTimer (chunkKey: string): void {
1229
+ private armExpectedGraceTimer(chunkKey: string): void {
1383
1230
  if (this.nearRevealGraceTimers.has(chunkKey)) return
1384
1231
  const timer = setTimeout(() => {
1385
1232
  this.nearRevealGraceTimers.delete(chunkKey)
@@ -1389,7 +1236,7 @@ export class ChunkMeshManager {
1389
1236
  this.nearRevealGraceTimers.set(chunkKey, timer)
1390
1237
  }
1391
1238
 
1392
- private clearExpectedGraceTimer (chunkKey: string): void {
1239
+ private clearExpectedGraceTimer(chunkKey: string): void {
1393
1240
  const timer = this.nearRevealGraceTimers.get(chunkKey)
1394
1241
  if (timer) {
1395
1242
  clearTimeout(timer)
@@ -1397,7 +1244,7 @@ export class ChunkMeshManager {
1397
1244
  }
1398
1245
  }
1399
1246
 
1400
- cleanupSection (sectionKey: string, opts?: { forRemesh?: boolean }) {
1247
+ cleanupSection(sectionKey: string, opts?: { forRemesh?: boolean }) {
1401
1248
  // Remove section object from scene
1402
1249
  const sectionObject = this.sectionObjects[sectionKey]
1403
1250
  if (sectionObject) {
@@ -1416,7 +1263,7 @@ export class ChunkMeshManager {
1416
1263
  // Cleanup banner textures before disposing
1417
1264
  if (sectionObject.bannersContainer) {
1418
1265
  for (const child of sectionObject.bannersContainer.children) {
1419
- const banner = child as THREE.Group & { bannerMaterial?: THREE.MeshBasicMaterial, bannerTexture?: THREE.Texture }
1266
+ const banner = child as THREE.Group & { bannerMaterial?: THREE.MeshBasicMaterial; bannerTexture?: THREE.Texture }
1420
1267
  if (banner.bannerMaterial) {
1421
1268
  this.blockEntityLightRegistry.unregister(banner.bannerMaterial)
1422
1269
  }
@@ -1426,11 +1273,13 @@ export class ChunkMeshManager {
1426
1273
  }
1427
1274
  this.disposeContainer(sectionObject.bannersContainer)
1428
1275
  }
1429
- this.globalBlockBuffer?.removeSection(sectionKey)
1430
- this.globalLegacyBuffer?.removeSection(sectionKey)
1431
- this.globalLegacyBlendBuffer?.removeSection(sectionKey)
1432
- this.maybeUnregisterLegacyCullSection(sectionKey)
1433
- this.unregisterShaderSectionRaycastBox(sectionKey)
1276
+ if (!opts?.forRemesh) {
1277
+ this.globalBlockBuffer?.removeSection(sectionKey)
1278
+ this.globalLegacyBuffer?.removeSection(sectionKey)
1279
+ this.globalLegacyBlendBuffer?.removeSection(sectionKey)
1280
+ this.maybeUnregisterLegacyCullSection(sectionKey)
1281
+ this.unregisterShaderSectionRaycastBox(sectionKey)
1282
+ }
1434
1283
  this.markCullDirty()
1435
1284
  delete sectionObject.deferredLegacyOpaque
1436
1285
  delete sectionObject.deferredLegacyBlend
@@ -1469,8 +1318,7 @@ export class ChunkMeshManager {
1469
1318
  }
1470
1319
  delete this.sectionObjects[sectionKey]
1471
1320
  if (!opts?.forRemesh) {
1472
- this.worldRenderer.getModule<{ onSectionRemoved?: (key: string) => void }>('futuristicReveal')
1473
- ?.onSectionRemoved?.(sectionKey)
1321
+ this.worldRenderer.getModule<{ onSectionRemoved?: (key: string) => void }>('futuristicReveal')?.onSectionRemoved?.(sectionKey)
1474
1322
  }
1475
1323
  }
1476
1324
  }
@@ -1478,7 +1326,7 @@ export class ChunkMeshManager {
1478
1326
  /**
1479
1327
  * Release a section and return its mesh to the pool
1480
1328
  */
1481
- private releasePooledMesh (sectionKey: string): void {
1329
+ private releasePooledMesh(sectionKey: string): void {
1482
1330
  const poolEntry = this.activeSections.get(sectionKey)
1483
1331
  if (!poolEntry) return
1484
1332
 
@@ -1491,7 +1339,7 @@ export class ChunkMeshManager {
1491
1339
  this.cleanupExcessMeshes()
1492
1340
  }
1493
1341
 
1494
- releaseSection (sectionKey: string): boolean {
1342
+ releaseSection(sectionKey: string): boolean {
1495
1343
  this.cleanupSection(sectionKey)
1496
1344
 
1497
1345
  const poolEntry = this.activeSections.get(sectionKey)
@@ -1519,14 +1367,14 @@ export class ChunkMeshManager {
1519
1367
  /**
1520
1368
  * Get section object if it exists
1521
1369
  */
1522
- getSectionObject (sectionKey: string): SectionObject | undefined {
1370
+ getSectionObject(sectionKey: string): SectionObject | undefined {
1523
1371
  return this.sectionObjects[sectionKey]
1524
1372
  }
1525
1373
 
1526
1374
  /**
1527
1375
  * Update box helper for a section
1528
1376
  */
1529
- updateBoxHelper (sectionKey: string, showChunkBorders: boolean, chunkBoxMaterial: THREE.Material = this.chunkBoxMaterial) {
1377
+ updateBoxHelper(sectionKey: string, showChunkBorders: boolean, chunkBoxMaterial: THREE.Material = this.chunkBoxMaterial) {
1530
1378
  const sectionObject = this.sectionObjects[sectionKey]
1531
1379
  if (!sectionObject) return
1532
1380
 
@@ -1562,7 +1410,7 @@ export class ChunkMeshManager {
1562
1410
  * after the move from `WorldBlockGeometry` (which created the helpers
1563
1411
  * eagerly per section) to the pooled `ChunkMeshManager`.
1564
1412
  */
1565
- updateAllBoxHelpers (showChunkBorders: boolean) {
1413
+ updateAllBoxHelpers(showChunkBorders: boolean) {
1566
1414
  for (const sectionKey of Object.keys(this.sectionObjects)) {
1567
1415
  this.updateBoxHelper(sectionKey, showChunkBorders)
1568
1416
  }
@@ -1571,21 +1419,21 @@ export class ChunkMeshManager {
1571
1419
  /**
1572
1420
  * Get mesh for section if it exists
1573
1421
  */
1574
- getSectionMesh (sectionKey: string): THREE.Mesh | undefined {
1422
+ getSectionMesh(sectionKey: string): THREE.Mesh | undefined {
1575
1423
  return this.activeSections.get(sectionKey)?.mesh
1576
1424
  }
1577
1425
 
1578
1426
  /**
1579
1427
  * Check if section is managed by this pool
1580
1428
  */
1581
- hasSection (sectionKey: string): boolean {
1429
+ hasSection(sectionKey: string): boolean {
1582
1430
  return this.activeSections.has(sectionKey)
1583
1431
  }
1584
1432
 
1585
1433
  /**
1586
1434
  * Update pool size based on new view distance
1587
1435
  */
1588
- updateViewDistance (maxViewDistance: number) {
1436
+ updateViewDistance(maxViewDistance: number) {
1589
1437
  // Calculate dynamic pool size based on view distance
1590
1438
  const chunksInView = (maxViewDistance * 2 + 1) ** 2
1591
1439
  const maxSectionsPerChunk = this.worldHeight / 16
@@ -1606,7 +1454,7 @@ export class ChunkMeshManager {
1606
1454
  /**
1607
1455
  * Get pool statistics
1608
1456
  */
1609
- getGlobalBufferStats (): GlobalBufferStats {
1457
+ getGlobalBufferStats(): GlobalBufferStats {
1610
1458
  const snapshotLegacy = (buffer: GlobalLegacyBuffer | null): GlobalBufferSlotStats | null => {
1611
1459
  if (!buffer) return null
1612
1460
  return {
@@ -1614,7 +1462,7 @@ export class ChunkMeshManager {
1614
1462
  capacity: buffer.getCapacityQuads(),
1615
1463
  sections: buffer.getSectionCount(),
1616
1464
  usedBytes: buffer.getUsedMemoryBytes(),
1617
- capacityBytes: buffer.getMemoryBytes(),
1465
+ capacityBytes: buffer.getMemoryBytes()
1618
1466
  }
1619
1467
  }
1620
1468
 
@@ -1626,17 +1474,17 @@ export class ChunkMeshManager {
1626
1474
  capacity: cubes.getCapacityFaces(),
1627
1475
  sections: cubes.getSectionCount(),
1628
1476
  usedBytes: cubes.getUsedMemoryBytes(),
1629
- capacityBytes: cubes.getMemoryBytes(),
1477
+ capacityBytes: cubes.getMemoryBytes()
1630
1478
  }
1631
1479
  : null,
1632
1480
  legacyOpaque: snapshotLegacy(this.globalLegacyBuffer),
1633
- legacyBlend: snapshotLegacy(this.globalLegacyBlendBuffer),
1481
+ legacyBlend: snapshotLegacy(this.globalLegacyBlendBuffer)
1634
1482
  }
1635
1483
  }
1636
1484
 
1637
- getStats () {
1485
+ getStats() {
1638
1486
  const freeCount = this.meshPool.filter(entry => !entry.inUse).length
1639
- const hitRate = this.hits + this.misses > 0 ? (this.hits / (this.hits + this.misses) * 100).toFixed(1) : '0'
1487
+ const hitRate = this.hits + this.misses > 0 ? ((this.hits / (this.hits + this.misses)) * 100).toFixed(1) : '0'
1640
1488
  const memoryUsage = this.getEstimatedMemoryUsage()
1641
1489
 
1642
1490
  return {
@@ -1653,21 +1501,21 @@ export class ChunkMeshManager {
1653
1501
  /**
1654
1502
  * Get total tiles rendered
1655
1503
  */
1656
- getTotalTiles (): number {
1504
+ getTotalTiles(): number {
1657
1505
  return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj.tilesCount || 0), 0)
1658
1506
  }
1659
1507
 
1660
1508
  /**
1661
1509
  * Get total blocks rendered
1662
1510
  */
1663
- getTotalBlocks (): number {
1511
+ getTotalBlocks(): number {
1664
1512
  return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj.blocksCount || 0), 0)
1665
1513
  }
1666
1514
 
1667
1515
  /**
1668
1516
  * Estimate memory usage in MB
1669
1517
  */
1670
- getEstimatedMemoryUsage (): { total: string, breakdown: any } {
1518
+ getEstimatedMemoryUsage(): { total: string; breakdown: any } {
1671
1519
  let totalBytes = 0
1672
1520
  let positionBytes = 0
1673
1521
  let normalBytes = 0
@@ -1756,7 +1604,7 @@ export class ChunkMeshManager {
1756
1604
  color: `${(colorBytes / (1024 * 1024)).toFixed(2)} MB`,
1757
1605
  uv: `${(uvBytes / (1024 * 1024)).toFixed(2)} MB`,
1758
1606
  index: `${(indexBytes / (1024 * 1024)).toFixed(2)} MB`,
1759
- shaderInstances: `${(shaderInstanceBytes / (1024 * 1024)).toFixed(2)} MB`,
1607
+ shaderInstances: `${(shaderInstanceBytes / (1024 * 1024)).toFixed(2)} MB`
1760
1608
  }
1761
1609
  }
1762
1610
  }
@@ -1764,7 +1612,7 @@ export class ChunkMeshManager {
1764
1612
  /**
1765
1613
  * Cleanup and dispose resources
1766
1614
  */
1767
- dispose () {
1615
+ dispose() {
1768
1616
  // Release all active sections (snapshot keys to avoid mutating map during iteration)
1769
1617
  const activeKeys = [...this.activeSections.keys()]
1770
1618
  for (const sectionKey of activeKeys) {
@@ -1783,6 +1631,7 @@ export class ChunkMeshManager {
1783
1631
  this.activeSections.clear()
1784
1632
  this.chunkBoxMaterial.dispose()
1785
1633
  this.shaderSectionRaycastBoxes.clear()
1634
+ this.lastBufferStateKey = ''
1786
1635
  this.globalBlockBuffer?.dispose()
1787
1636
  this.globalBlockBuffer = null
1788
1637
  this.globalLegacyBuffer?.dispose()
@@ -1807,7 +1656,7 @@ export class ChunkMeshManager {
1807
1656
 
1808
1657
  // Private helper methods
1809
1658
 
1810
- private acquireMesh (): ChunkMeshPool | undefined {
1659
+ private acquireMesh(): ChunkMeshPool | undefined {
1811
1660
  if (this.bypassPooling) {
1812
1661
  const entry: ChunkMeshPool = {
1813
1662
  mesh: new THREE.Mesh(new THREE.BufferGeometry(), this.getLegacyShaderMaterial()),
@@ -1854,7 +1703,7 @@ export class ChunkMeshManager {
1854
1703
  throw new Error('ChunkMeshManager: Failed to acquire mesh after pool expansion')
1855
1704
  }
1856
1705
 
1857
- private expandPool (newSize: number) {
1706
+ private expandPool(newSize: number) {
1858
1707
  const currentLength = this.meshPool.length
1859
1708
  this.poolSize = newSize
1860
1709
 
@@ -1877,12 +1726,7 @@ export class ChunkMeshManager {
1877
1726
  }
1878
1727
  }
1879
1728
 
1880
- private updateGeometryAttribute (
1881
- geometry: THREE.BufferGeometry,
1882
- name: string,
1883
- array: Float32Array,
1884
- itemSize: number
1885
- ) {
1729
+ private updateGeometryAttribute(geometry: THREE.BufferGeometry, name: string, array: Float32Array, itemSize: number) {
1886
1730
  const attribute = geometry.getAttribute(name)
1887
1731
 
1888
1732
  if (attribute && attribute.count === array.length / itemSize) {
@@ -1895,7 +1739,7 @@ export class ChunkMeshManager {
1895
1739
  }
1896
1740
  }
1897
1741
 
1898
- private clearGeometry (geometry: THREE.BufferGeometry) {
1742
+ private clearGeometry(geometry: THREE.BufferGeometry) {
1899
1743
  const attributes = ['position', 'normal', 'color', 'a_skyLight', 'a_blockLight', 'uv']
1900
1744
  for (const name of attributes) {
1901
1745
  if (geometry.hasAttribute(name)) {
@@ -1909,7 +1753,7 @@ export class ChunkMeshManager {
1909
1753
  geometry.boundingSphere = null
1910
1754
  }
1911
1755
 
1912
- private cleanupExcessMeshes () {
1756
+ private cleanupExcessMeshes() {
1913
1757
  // If pool size exceeds max and we have free meshes, remove some
1914
1758
  if (this.poolSize > this.maxPoolSize) {
1915
1759
  const freeCount = this.meshPool.filter(entry => !entry.inUse).length
@@ -1929,14 +1773,14 @@ export class ChunkMeshManager {
1929
1773
  }
1930
1774
  }
1931
1775
 
1932
- private disposeContainer (container: THREE.Group, cleanTextures = true) {
1776
+ private disposeContainer(container: THREE.Group, cleanTextures = true) {
1933
1777
  disposeObject(container, cleanTextures)
1934
1778
  }
1935
1779
 
1936
1780
  /**
1937
1781
  * Record render time for performance monitoring
1938
1782
  */
1939
- recordRenderTime (renderTime: number): void {
1783
+ recordRenderTime(renderTime: number): void {
1940
1784
  this.renderTimes.push(renderTime)
1941
1785
  if (this.renderTimes.length > this.maxRenderTimeSamples) {
1942
1786
  this.renderTimes.shift()
@@ -1953,31 +1797,31 @@ export class ChunkMeshManager {
1953
1797
  /**
1954
1798
  * Get current effective render distance
1955
1799
  */
1956
- getEffectiveRenderDistance (): number {
1800
+ getEffectiveRenderDistance(): number {
1957
1801
  return this.performanceOverrideDistance || this.worldRenderer.viewDistance
1958
1802
  }
1959
1803
 
1960
1804
  /**
1961
1805
  * Force reset performance override
1962
1806
  */
1963
- resetPerformanceOverride (): void {
1807
+ resetPerformanceOverride(): void {
1964
1808
  this.performanceOverrideDistance = undefined
1965
1809
  this.renderTimes.length = 0
1966
1810
  console.log('ChunkMeshManager: Performance override reset')
1967
1811
  }
1968
1812
 
1969
1813
  /**
1970
- * Get average render time
1971
- */
1972
- getAverageRenderTime (): number {
1814
+ * Get average render time
1815
+ */
1816
+ getAverageRenderTime(): number {
1973
1817
  if (this.renderTimes.length === 0) return 0
1974
1818
  return this.renderTimes.reduce((sum, time) => sum + time, 0) / this.renderTimes.length
1975
1819
  }
1976
1820
 
1977
1821
  /**
1978
- * Check if performance is degraded and adjust render distance
1979
- */
1980
- private checkPerformance (): void {
1822
+ * Check if performance is degraded and adjust render distance
1823
+ */
1824
+ private checkPerformance(): void {
1981
1825
  if (this.renderTimes.length < this.maxRenderTimeSamples) return
1982
1826
 
1983
1827
  const avgRenderTime = this.getAverageRenderTime()
@@ -2008,7 +1852,7 @@ export class ChunkMeshManager {
2008
1852
  /**
2009
1853
  * Hide sections beyond performance override distance
2010
1854
  */
2011
- updateSectionsVisibility (): void {
1855
+ updateSectionsVisibility(): void {
2012
1856
  const cameraPos = this.worldRenderer.cameraSectionPos
2013
1857
  for (const [sectionKey, sectionObject] of Object.entries(this.sectionObjects)) {
2014
1858
  // Don't override "Batch Chunks Display" hiding — those sections must
@@ -2035,16 +1879,14 @@ export class ChunkMeshManager {
2035
1879
  }
2036
1880
  }
2037
1881
 
2038
-
2039
1882
  class SignHeadsRenderer {
2040
- constructor (public worldRendererThree: WorldRendererThree) {
2041
- }
1883
+ constructor(public worldRendererThree: WorldRendererThree) {}
2042
1884
 
2043
- dispose () {
1885
+ dispose() {
2044
1886
  disposeAllSignTextures()
2045
1887
  }
2046
1888
 
2047
- renderHead (position: Vec3, rotation: number, isWall: boolean, blockEntity) {
1889
+ renderHead(position: Vec3, rotation: number, isWall: boolean, blockEntity) {
2048
1890
  let textureData: string
2049
1891
  if (blockEntity.SkullOwner) {
2050
1892
  textureData = blockEntity.SkullOwner.Properties?.textures?.[0]?.Value
@@ -2058,8 +1900,7 @@ class SignHeadsRenderer {
2058
1900
  let skinUrl = decodedData.textures?.SKIN?.url
2059
1901
  const { skinTexturesProxy } = this.worldRendererThree.worldRendererConfig
2060
1902
  if (skinTexturesProxy) {
2061
- skinUrl = skinUrl?.replace('http://textures.minecraft.net/', skinTexturesProxy)
2062
- .replace('https://textures.minecraft.net/', skinTexturesProxy)
1903
+ skinUrl = skinUrl?.replace('http://textures.minecraft.net/', skinTexturesProxy).replace('https://textures.minecraft.net/', skinTexturesProxy)
2063
1904
  }
2064
1905
 
2065
1906
  const mesh = getMesh(this.worldRendererThree, skinUrl, armorModel.head as any)
@@ -2072,11 +1913,7 @@ class SignHeadsRenderer {
2072
1913
  group.add(mesh)
2073
1914
  this.worldRendererThree.sceneOrigin.track(group)
2074
1915
  group.position.set(position.x + 0.5, position.y + 0.045, position.z + 0.5)
2075
- group.rotation.set(
2076
- 0,
2077
- -THREE.MathUtils.degToRad(rotation * (isWall ? 90 : 45 / 2)),
2078
- 0
2079
- )
1916
+ group.rotation.set(0, -THREE.MathUtils.degToRad(rotation * (isWall ? 90 : 45 / 2)), 0)
2080
1917
  group.scale.set(0.8, 0.8, 0.8)
2081
1918
  return group
2082
1919
  } catch (err) {
@@ -2084,7 +1921,7 @@ class SignHeadsRenderer {
2084
1921
  }
2085
1922
  }
2086
1923
 
2087
- renderSign (position: Vec3, rotation: number, isWall: boolean, isHanging: boolean, blockEntity) {
1924
+ renderSign(position: Vec3, rotation: number, isWall: boolean, isHanging: boolean, blockEntity) {
2088
1925
  const tex = getSignTexture(this.worldRendererThree, blockEntity, isHanging)
2089
1926
 
2090
1927
  if (!tex) return
@@ -2105,11 +1942,7 @@ class SignHeadsRenderer {
2105
1942
  }
2106
1943
 
2107
1944
  const group = new THREE.Group() as THREE.Group & { signTexture?: THREE.Texture }
2108
- group.rotation.set(
2109
- 0,
2110
- -THREE.MathUtils.degToRad(rotation * (isWall ? 90 : 45 / 2)),
2111
- 0
2112
- )
1945
+ group.rotation.set(0, -THREE.MathUtils.degToRad(rotation * (isWall ? 90 : 45 / 2)), 0)
2113
1946
  group.add(mesh)
2114
1947
  group.signTexture = tex
2115
1948
  const height = (isHanging ? 10 : 8) / 16