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
@@ -9,7 +9,15 @@ import { proxy, subscribe } from 'valtio'
9
9
  import type { ResourcesManagerTransferred } from '../resourcesManager/resourcesManager'
10
10
  import { dynamicMcDataFiles } from './buildSharedConfig.mjs'
11
11
  import { DisplayWorldOptions, GraphicsInitOptions, RendererReactiveState, SoundSystem } from '../graphicsBackend/types'
12
- import { HighestBlockInfo, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey, MesherConfig, MesherMainEvent, SECTION_HEIGHT } from '../mesher-shared/shared'
12
+ import {
13
+ HighestBlockInfo,
14
+ CustomBlockModels,
15
+ BlockStateModelInfo,
16
+ getBlockAssetsCacheKey,
17
+ MesherConfig,
18
+ MesherMainEvent,
19
+ SECTION_HEIGHT
20
+ } from '../mesher-shared/shared'
13
21
  import { chunkPos } from './simpleUtils'
14
22
  import { addNewStat, MC_RENDERER_DEBUG_OVERLAY_CLASS, removeAllStats, updatePanesVisibility, updateStatText } from './ui/newStats'
15
23
  import { getPlayerStateUtils } from '../graphicsBackend/playerState'
@@ -24,6 +32,7 @@ import { generateSpiralMatrix } from './spiral'
24
32
  import { PlayerStateReactive } from '../playerState/playerState'
25
33
  import { IndexedData } from 'minecraft-data'
26
34
  import { WorldRendererConfig } from '../graphicsBackend/config'
35
+ import { CameraCollisionBlockCache } from '../three/cameraCollisionBlockCache'
27
36
 
28
37
  function mod(x, n) {
29
38
  return ((x % n) + n) % n
@@ -35,6 +44,8 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
35
44
  timeOfTheDay = 0
36
45
  lastMesherSkyLight = 15
37
46
  worldSizeParams = { minY: 0, worldHeight: 256 }
47
+ /** Block columns for third-person voxel DDA (renderer thread). */
48
+ cameraCollisionBlockCache: CameraCollisionBlockCache
38
49
  reactiveDebugParams = proxy({
39
50
  stopRendering: false,
40
51
  chunksRenderAboveOverride: undefined as number | undefined,
@@ -43,7 +54,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
43
54
  chunksRenderBelowEnabled: false,
44
55
  chunksRenderDistanceOverride: undefined as number | undefined,
45
56
  chunksRenderDistanceEnabled: false,
46
- disableEntities: false,
57
+ disableEntities: false
47
58
  // disableParticles: false
48
59
  })
49
60
 
@@ -90,7 +101,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
90
101
  isProcessingQueue = false
91
102
  ONMESSAGE_TIME_LIMIT = 30 // ms
92
103
 
93
- handleResize = () => { }
104
+ handleResize = () => {}
94
105
  highestBlocksByChunks = new Map<string, { [chunkKey: string]: HighestBlockInfo }>()
95
106
  blockEntities = {}
96
107
 
@@ -131,7 +142,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
131
142
  private mesherPoolSnapshot = {
132
143
  mesherWorkers: -1,
133
144
  wasmMesher: false,
134
- dedicatedChangeWorker: false,
145
+ dedicatedChangeWorker: false
135
146
  }
136
147
  private mesherReconfigureQueue: Promise<void> = Promise.resolve()
137
148
  private heightmapDebounceTimers = new Map<string, ReturnType<typeof setTimeout>>()
@@ -203,7 +214,12 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
203
214
  return (this.initOptions.config.statsVisible ?? 0) > 1
204
215
  }
205
216
 
206
- constructor(public readonly resourcesManager: ResourcesManagerTransferred, public displayOptions: DisplayWorldOptions, public initOptions: GraphicsInitOptions) {
217
+ constructor(
218
+ public readonly resourcesManager: ResourcesManagerTransferred,
219
+ public displayOptions: DisplayWorldOptions,
220
+ public initOptions: GraphicsInitOptions
221
+ ) {
222
+ this.cameraCollisionBlockCache = new CameraCollisionBlockCache(this.displayOptions.version)
207
223
  this.snapshotInitialValues()
208
224
  this.worldRendererConfig = displayOptions.inWorldRenderingConfig
209
225
  this.playerStateReactive = displayOptions.playerStateReactive!
@@ -270,12 +286,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
270
286
  if (!this.active) return
271
287
  await this.updateAssetsData()
272
288
  }
273
- bindAbortableEmitterListener(
274
- this.resourcesManager,
275
- 'assetsTexturesUpdated',
276
- onAssetsTexturesUpdated,
277
- this.abortController.signal
278
- )
289
+ bindAbortableEmitterListener(this.resourcesManager, 'assetsTexturesUpdated', onAssetsTexturesUpdated, this.abortController.signal)
279
290
 
280
291
  this.watchReactivePlayerState()
281
292
  this.watchReactiveConfig()
@@ -283,7 +294,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
283
294
  this.worldReadyResolvers.resolve()
284
295
  }
285
296
 
286
- snapshotInitialValues() { }
297
+ snapshotInitialValues() {}
287
298
 
288
299
  wasChunkSentToWorker(chunkKey: string) {
289
300
  return this.loadedChunks[chunkKey]
@@ -305,7 +316,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
305
316
  }
306
317
  }
307
318
 
308
- async getBlockInfo(blockPos: { x: number, y: number, z: number }, stateId: number) {
319
+ async getBlockInfo(blockPos: { x: number; y: number; z: number }, stateId: number) {
309
320
  const CHUNK_SIZE = 16
310
321
  const chunkKey = `${Math.floor(blockPos.x / CHUNK_SIZE) * CHUNK_SIZE},${Math.floor(blockPos.z / CHUNK_SIZE) * CHUNK_SIZE}`
311
322
  const customBlockName = this.protocolCustomBlocks.get(chunkKey)?.[`${blockPos.x},${blockPos.y},${blockPos.z}`]
@@ -323,14 +334,17 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
323
334
 
324
335
  private createMesherWorker() {
325
336
  const script = this.getMesherWorkerScript()
326
- return initMesherWorker((data) => {
327
- if (Array.isArray(data)) {
328
- this.messageQueue.push(...data)
329
- } else {
330
- this.messageQueue.push(data)
331
- }
332
- void this.processMessageQueue('worker')
333
- }, script === 'wasm' ? 'mesherWasm.js' : 'mesher.js')
337
+ return initMesherWorker(
338
+ data => {
339
+ if (Array.isArray(data)) {
340
+ this.messageQueue.push(...data)
341
+ } else {
342
+ this.messageQueue.push(data)
343
+ }
344
+ void this.processMessageQueue('worker')
345
+ },
346
+ script === 'wasm' ? 'mesherWasm.js' : 'mesher.js'
347
+ )
334
348
  }
335
349
 
336
350
  initWorkers(numWorkers = this.worldRendererConfig.mesherWorkers) {
@@ -343,7 +357,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
343
357
  this.mesherPoolSnapshot = {
344
358
  mesherWorkers: this.worldRendererConfig.mesherWorkers,
345
359
  wasmMesher: this.worldRendererConfig.wasmMesher,
346
- dedicatedChangeWorker: this.worldRendererConfig.dedicatedChangeWorker,
360
+ dedicatedChangeWorker: this.worldRendererConfig.dedicatedChangeWorker
347
361
  }
348
362
  }
349
363
 
@@ -353,11 +367,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
353
367
  const tryReconfigure = () => {
354
368
  const cfg = this.worldRendererConfig
355
369
  const snap = this.mesherPoolSnapshot
356
- if (
357
- cfg.mesherWorkers === snap.mesherWorkers &&
358
- cfg.wasmMesher === snap.wasmMesher &&
359
- cfg.dedicatedChangeWorker === snap.dedicatedChangeWorker
360
- ) {
370
+ if (cfg.mesherWorkers === snap.mesherWorkers && cfg.wasmMesher === snap.wasmMesher && cfg.dedicatedChangeWorker === snap.dedicatedChangeWorker) {
361
371
  return
362
372
  }
363
373
  this.syncMesherPoolSnapshot()
@@ -365,16 +375,14 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
365
375
  }
366
376
 
367
377
  for (const key of ['mesherWorkers', 'wasmMesher', 'dedicatedChangeWorker'] as const) {
368
- this.valtioUnsubs.push(
369
- this.onReactiveConfigUpdated(key, tryReconfigure, false)
370
- )
378
+ this.valtioUnsubs.push(this.onReactiveConfigUpdated(key, tryReconfigure, false))
371
379
  }
372
380
  }
373
381
 
374
382
  private enqueueMesherWorkersReconfigure() {
375
383
  this.mesherReconfigureQueue = this.mesherReconfigureQueue
376
384
  .then(() => this.reconfigureMesherWorkers())
377
- .catch((err) => {
385
+ .catch(err => {
378
386
  console.error('[Mesher] Failed to reconfigure workers:', err)
379
387
  })
380
388
  }
@@ -450,7 +458,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
450
458
 
451
459
  onReactiveConfigUpdated<T extends keyof typeof this.worldRendererConfig>(
452
460
  key: T,
453
- callback: (value: typeof this.worldRendererConfig[T]) => void,
461
+ callback: (value: (typeof this.worldRendererConfig)[T]) => void,
454
462
  initial = true
455
463
  ) {
456
464
  if (initial) {
@@ -462,19 +470,19 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
462
470
  return subscribeKey(this.worldRendererConfig, key, callback)
463
471
  }
464
472
 
465
- onReactiveDebugUpdated<T extends keyof typeof this.reactiveDebugParams>(key: T, callback: (value: typeof this.reactiveDebugParams[T]) => void) {
473
+ onReactiveDebugUpdated<T extends keyof typeof this.reactiveDebugParams>(key: T, callback: (value: (typeof this.reactiveDebugParams)[T]) => void) {
466
474
  callback(this.reactiveDebugParams[key])
467
475
  subscribeKey(this.reactiveDebugParams, key, callback)
468
476
  }
469
477
 
470
478
  watchReactivePlayerState() {
471
479
  this.valtioUnsubs.push(
472
- this.onReactivePlayerStateUpdated('backgroundColor', (value) => {
480
+ this.onReactivePlayerStateUpdated('backgroundColor', value => {
473
481
  this.changeBackgroundColor(value)
474
482
  })
475
483
  )
476
484
  this.valtioUnsubs.push(
477
- this.onReactivePlayerStateUpdated('cardinalLight', (value) => {
485
+ this.onReactivePlayerStateUpdated('cardinalLight', value => {
478
486
  this.changeCardinalLight(value)
479
487
  })
480
488
  )
@@ -482,7 +490,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
482
490
 
483
491
  watchReactiveConfig() {
484
492
  this.valtioUnsubs.push(
485
- this.onReactiveConfigUpdated('fetchPlayerSkins', (value) => {
493
+ this.onReactiveConfigUpdated('fetchPlayerSkins', value => {
486
494
  setSkinsConfig({ apiEnabled: value })
487
495
  })
488
496
  )
@@ -491,7 +499,12 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
491
499
  async processMessageQueue(source: string) {
492
500
  if (this.isProcessingQueue || this.messageQueue.length === 0) return
493
501
  this.logWorkerWork(`# ${source} processing queue`)
494
- if (this.lastRendered && performance.now() - this.lastRendered > this.ONMESSAGE_TIME_LIMIT && this.worldRendererConfig._experimentalSmoothChunkLoading && this.renderingActive) {
502
+ if (
503
+ this.lastRendered &&
504
+ performance.now() - this.lastRendered > this.ONMESSAGE_TIME_LIMIT &&
505
+ this.worldRendererConfig._experimentalSmoothChunkLoading &&
506
+ this.renderingActive
507
+ ) {
495
508
  const start = performance.now()
496
509
  await new Promise(resolve => {
497
510
  requestAnimationFrame(resolve)
@@ -512,7 +525,10 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
512
525
  }
513
526
 
514
527
  // Check if we've exceeded the time limit
515
- if (processingStopped || (performance.now() - startTime > this.ONMESSAGE_TIME_LIMIT && this.renderingActive && this.worldRendererConfig._experimentalSmoothChunkLoading)) {
528
+ if (
529
+ processingStopped ||
530
+ (performance.now() - startTime > this.ONMESSAGE_TIME_LIMIT && this.renderingActive && this.worldRendererConfig._experimentalSmoothChunkLoading)
531
+ ) {
516
532
  // If we have more messages and exceeded time limit, schedule next batch
517
533
  if (this.messageQueue.length > 0) {
518
534
  requestAnimationFrame(async () => {
@@ -544,7 +560,8 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
544
560
  const chunkCoords = data.key.split(',').map(Number)
545
561
  this.lastChunkDistance = Math.max(...this.getDistance(new Vec3(chunkCoords[0], 0, chunkCoords[2])))
546
562
  }
547
- if (data.type === 'sectionFinished') { // on after load & unload section
563
+ if (data.type === 'sectionFinished') {
564
+ // on after load & unload section
548
565
  this.logWorkerWork(`<- ${data.workerIndex} sectionFinished ${data.key} ${JSON.stringify({ processTime: data.processTime })}`)
549
566
  if (!this.sectionsWaiting.has(data.key)) {
550
567
  console.debug(`sectionFinished for non-outstanding section ${data.key} (viewDistance=${this.viewDistance})`)
@@ -558,7 +575,8 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
558
575
 
559
576
  const chunkCoords = data.key.split(',').map(Number)
560
577
  const chunkKey = `${chunkCoords[0]},${chunkCoords[2]}`
561
- if (this.loadedChunks[chunkKey]) { // ensure chunk data was added, not a neighbor chunk update
578
+ if (this.loadedChunks[chunkKey]) {
579
+ // ensure chunk data was added, not a neighbor chunk update
562
580
  let loaded = true
563
581
  const sectionHeight = this.getSectionHeight()
564
582
  for (let y = this.worldMinYRender; y < this.worldSizeParams.worldHeight; y += sectionHeight) {
@@ -601,15 +619,15 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
601
619
  this.renderUpdateEmitter.emit('update')
602
620
  if (data.processTime) {
603
621
  this.workersProcessAverageTimeCount++
604
- this.workersProcessAverageTime = ((this.workersProcessAverageTime * (this.workersProcessAverageTimeCount - 1)) + data.processTime) / this.workersProcessAverageTimeCount
622
+ this.workersProcessAverageTime =
623
+ (this.workersProcessAverageTime * (this.workersProcessAverageTimeCount - 1) + data.processTime) / this.workersProcessAverageTimeCount
605
624
  this.maxWorkersProcessTime = Math.max(this.maxWorkersProcessTime, data.processTime)
606
625
  }
607
- if (typeof data.pre === 'number' && typeof data.wasm === 'number' && typeof data.post === 'number'
608
- && (data.pre > 0 || data.wasm > 0 || data.post > 0)) {
626
+ if (typeof data.pre === 'number' && typeof data.wasm === 'number' && typeof data.post === 'number' && (data.pre > 0 || data.wasm > 0 || data.post > 0)) {
609
627
  const n = ++this.workersPhaseSampleCount
610
- this.workersPreAverageTime = ((this.workersPreAverageTime * (n - 1)) + data.pre) / n
611
- this.workersWasmAverageTime = ((this.workersWasmAverageTime * (n - 1)) + data.wasm) / n
612
- this.workersPostAverageTime = ((this.workersPostAverageTime * (n - 1)) + data.post) / n
628
+ this.workersPreAverageTime = (this.workersPreAverageTime * (n - 1) + data.pre) / n
629
+ this.workersWasmAverageTime = (this.workersWasmAverageTime * (n - 1) + data.wasm) / n
630
+ this.workersPostAverageTime = (this.workersPostAverageTime * (n - 1) + data.post) / n
613
631
  // Pre-stage substages — additive schema; treat undefined as 0 so
614
632
  // events from older mesher builds don't poison the running mean.
615
633
  const ptc = typeof data.preTargetConvert === 'number' ? data.preTargetConvert : 0
@@ -619,11 +637,11 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
619
637
  const po = typeof data.preOther === 'number' ? data.preOther : 0
620
638
  const pch = typeof data.preCacheHits === 'number' ? data.preCacheHits : 0
621
639
  const pcm = typeof data.preCacheMisses === 'number' ? data.preCacheMisses : 0
622
- this.workersPreTargetConvertAverageTime = ((this.workersPreTargetConvertAverageTime * (n - 1)) + ptc) / n
623
- this.workersPreNeighborConvertAverageTime = ((this.workersPreNeighborConvertAverageTime * (n - 1)) + pnc) / n
624
- this.workersPreNeighborCountAverage = ((this.workersPreNeighborCountAverage * (n - 1)) + pncn) / n
625
- this.workersPreTypedArrayBuildAverageTime = ((this.workersPreTypedArrayBuildAverageTime * (n - 1)) + ptab) / n
626
- this.workersPreOtherAverageTime = ((this.workersPreOtherAverageTime * (n - 1)) + po) / n
640
+ this.workersPreTargetConvertAverageTime = (this.workersPreTargetConvertAverageTime * (n - 1) + ptc) / n
641
+ this.workersPreNeighborConvertAverageTime = (this.workersPreNeighborConvertAverageTime * (n - 1) + pnc) / n
642
+ this.workersPreNeighborCountAverage = (this.workersPreNeighborCountAverage * (n - 1) + pncn) / n
643
+ this.workersPreTypedArrayBuildAverageTime = (this.workersPreTypedArrayBuildAverageTime * (n - 1) + ptab) / n
644
+ this.workersPreOtherAverageTime = (this.workersPreOtherAverageTime * (n - 1) + po) / n
627
645
  this.workersPreCacheHitsTotal += pch
628
646
  this.workersPreCacheMissesTotal += pcm
629
647
  if (this.worldRendererConfig.debugWasmPerf && n % WorldRendererCommon.PHASE_PERF_LOG_INTERVAL === 0) {
@@ -641,7 +659,9 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
641
659
  const cacheTotal = this.workersPreCacheHitsTotal + this.workersPreCacheMissesTotal
642
660
  const cacheHitPct = cacheTotal > 0 ? (this.workersPreCacheHitsTotal / cacheTotal) * 100 : 0
643
661
  // eslint-disable-next-line no-console
644
- console.log(`[wasm-mesher perf] n=${n} pre=${this.workersPreAverageTime.toFixed(2)}ms (${prePct.toFixed(1)}%) wasm=${this.workersWasmAverageTime.toFixed(2)}ms (${wasmPct.toFixed(1)}%) post=${this.workersPostAverageTime.toFixed(2)}ms (${postPct.toFixed(1)}%) | pre.targetConvert=${this.workersPreTargetConvertAverageTime.toFixed(2)}ms (${tgtPct.toFixed(1)}%) pre.neighborConvert=${this.workersPreNeighborConvertAverageTime.toFixed(2)}ms (${nbrPct.toFixed(1)}%) [n̄=${nbrCnt.toFixed(2)}, per-nbr=${nbrPerAvg.toFixed(2)}ms] pre.typedArrayBuild=${this.workersPreTypedArrayBuildAverageTime.toFixed(2)}ms (${tabPct.toFixed(1)}%) pre.other=${this.workersPreOtherAverageTime.toFixed(2)}ms (${othPct.toFixed(1)}%) | pre.cache hits=${this.workersPreCacheHitsTotal} misses=${this.workersPreCacheMissesTotal} (${cacheHitPct.toFixed(1)}% hit)`)
662
+ console.log(
663
+ `[wasm-mesher perf] n=${n} pre=${this.workersPreAverageTime.toFixed(2)}ms (${prePct.toFixed(1)}%) wasm=${this.workersWasmAverageTime.toFixed(2)}ms (${wasmPct.toFixed(1)}%) post=${this.workersPostAverageTime.toFixed(2)}ms (${postPct.toFixed(1)}%) | pre.targetConvert=${this.workersPreTargetConvertAverageTime.toFixed(2)}ms (${tgtPct.toFixed(1)}%) pre.neighborConvert=${this.workersPreNeighborConvertAverageTime.toFixed(2)}ms (${nbrPct.toFixed(1)}%) [n̄=${nbrCnt.toFixed(2)}, per-nbr=${nbrPerAvg.toFixed(2)}ms] pre.typedArrayBuild=${this.workersPreTypedArrayBuildAverageTime.toFixed(2)}ms (${tabPct.toFixed(1)}%) pre.other=${this.workersPreOtherAverageTime.toFixed(2)}ms (${othPct.toFixed(1)}%) | pre.cache hits=${this.workersPreCacheHitsTotal} misses=${this.workersPreCacheMissesTotal} (${cacheHitPct.toFixed(1)}% hit)`
664
+ )
645
665
  }
646
666
  }
647
667
  }
@@ -679,7 +699,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
679
699
  this.updateChunksStats()
680
700
  }
681
701
 
682
- changeHandSwingingState(isAnimationPlaying: boolean, isLeftHand: boolean): void { }
702
+ changeHandSwingingState(isAnimationPlaying: boolean, isLeftHand: boolean): void {}
683
703
 
684
704
  abstract handleWorkerMessage(data: WorkerReceive): void
685
705
 
@@ -769,7 +789,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
769
789
 
770
790
  getMesherConfig(): MesherConfig {
771
791
  const timeOfDay = this.timeOfTheDay
772
- const skyLight = (timeOfDay < 0 || timeOfDay > 24_000) ? 15 : calculateSkyLightSimple(timeOfDay)
792
+ const skyLight = timeOfDay < 0 || timeOfDay > 24_000 ? 15 : calculateSkyLightSimple(timeOfDay)
773
793
  return {
774
794
  version: this.version,
775
795
  enableLighting: this.worldRendererConfig.enableLighting,
@@ -786,7 +806,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
786
806
  worldMaxY: this.worldMinYRender + this.worldSizeParams.worldHeight,
787
807
  disableConversionCache: this.worldRendererConfig.disableMesherConversionCache,
788
808
  computeWireframeEdges: this.worldRendererConfig.futuristicReveal === true,
789
- shaderCubeBlocks: this.isShaderCubeBlocksEnabled(),
809
+ shaderCubeBlocks: this.isShaderCubeBlocksEnabled()
790
810
  }
791
811
  }
792
812
 
@@ -810,7 +830,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
810
830
  latest: resources.blocksAtlasJson
811
831
  },
812
832
  blockstatesModels,
813
- config: this.getMesherConfig(),
833
+ config: this.getMesherConfig()
814
834
  })
815
835
  }
816
836
 
@@ -868,11 +888,15 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
868
888
  this.workers[0].postMessage({
869
889
  type: 'getHeightmap',
870
890
  x,
871
- z,
891
+ z
872
892
  })
873
893
  }
874
- this.logWorkerWork(() => `-> chunk ${JSON.stringify({ x, z, chunkLength: chunk.length, customBlockModelsLength: customBlockModels ? Object.keys(customBlockModels).length : 0 })}`)
894
+ this.logWorkerWork(
895
+ () =>
896
+ `-> chunk ${JSON.stringify({ x, z, chunkLength: chunk.length, customBlockModelsLength: customBlockModels ? Object.keys(customBlockModels).length : 0 })}`
897
+ )
875
898
  this.mesherLogReader?.chunkReceived(x, z, chunk.length)
899
+ this.cameraCollisionBlockCache.ingestColumn(x, z, chunk)
876
900
  const sectionHeight = this.getSectionHeight()
877
901
  const CHUNK_SIZE = 16
878
902
 
@@ -933,12 +957,15 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
933
957
  const sectionKey = `${x},${y},${z}`
934
958
  const waitingCount = this.sectionsWaiting.get(sectionKey)
935
959
  if (waitingCount !== undefined && waitingCount > 0) {
936
- console.debug(`[removeColumn] clearing non-zero sectionsWaiting for ${sectionKey}: ${waitingCount} (chunk ${x},${z}, viewDistance=${this.viewDistance})`)
960
+ console.debug(
961
+ `[removeColumn] clearing non-zero sectionsWaiting for ${sectionKey}: ${waitingCount} (chunk ${x},${z}, viewDistance=${this.viewDistance})`
962
+ )
937
963
  }
938
964
  this.sectionsWaiting.delete(sectionKey)
939
965
  delete this.finishedSections[sectionKey]
940
966
  }
941
967
  this.highestBlocksByChunks.delete(`${x},${z}`)
968
+ this.cameraCollisionBlockCache.removeColumn(x, z)
942
969
  const heightmapKey = `${Math.floor(x / 16)},${Math.floor(z / 16)}`
943
970
  delete this.reactiveState.world.heightmaps[heightmapKey]
944
971
 
@@ -971,126 +998,208 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
971
998
  void set()
972
999
  }
973
1000
 
974
- updateEntity(e: any, isUpdate = false) { }
1001
+ updateEntity(e: any, isUpdate = false) {}
975
1002
 
976
1003
  abstract updatePlayerEntity?(e: any): void
977
1004
 
978
- lightUpdate(chunkX: number, chunkZ: number) { }
1005
+ lightUpdate(chunkX: number, chunkZ: number) {}
979
1006
 
980
1007
  connect(worldView: WorldViewWorker) {
981
1008
  const worldEmitter = worldView
982
1009
  const signal = this.abortController.signal
983
1010
 
984
- bindAbortableListener(worldEmitter, 'entity', (e) => {
985
- this.updateEntity(e, false)
986
- }, signal)
987
- bindAbortableListener(worldEmitter, 'entityMoved', (e) => {
988
- this.updateEntity(e, true)
989
- }, signal)
990
- bindAbortableListener(worldEmitter, 'playerEntity', (e) => {
991
- this.updatePlayerEntity?.(e)
992
- }, signal)
1011
+ bindAbortableListener(
1012
+ worldEmitter,
1013
+ 'entity',
1014
+ e => {
1015
+ this.updateEntity(e, false)
1016
+ },
1017
+ signal
1018
+ )
1019
+ bindAbortableListener(
1020
+ worldEmitter,
1021
+ 'entityMoved',
1022
+ e => {
1023
+ this.updateEntity(e, true)
1024
+ },
1025
+ signal
1026
+ )
1027
+ bindAbortableListener(
1028
+ worldEmitter,
1029
+ 'playerEntity',
1030
+ e => {
1031
+ this.updatePlayerEntity?.(e)
1032
+ },
1033
+ signal
1034
+ )
993
1035
 
994
1036
  let currentLoadChunkBatch = null as {
995
1037
  timeout
996
1038
  data
997
1039
  } | null
998
- bindAbortableListener(worldEmitter, 'loadChunk', ({ x, z, chunk, worldConfig, isLightUpdate }) => {
999
- this.worldSizeParams = worldConfig
1000
- this.queuedChunks.add(`${x},${z}`)
1001
- const args = [x, z, chunk, isLightUpdate]
1002
- if (!currentLoadChunkBatch) {
1003
- // add a setting to use debounce instead
1004
- currentLoadChunkBatch = {
1005
- data: [],
1006
- timeout: setTimeout(() => {
1007
- for (const args of currentLoadChunkBatch!.data) {
1008
- this.queuedChunks.delete(`${args[0]},${args[1]}`)
1009
- this.addColumn(...args as Parameters<typeof this.addColumn>)
1010
- }
1011
- for (const fn of this.queuedFunctions) {
1012
- fn()
1013
- }
1014
- this.queuedFunctions = []
1015
- currentLoadChunkBatch = null
1016
- }, this.worldRendererConfig.addChunksBatchWaitTime)
1040
+ bindAbortableListener(
1041
+ worldEmitter,
1042
+ 'loadChunk',
1043
+ ({ x, z, chunk, worldConfig, isLightUpdate }) => {
1044
+ this.worldSizeParams = worldConfig
1045
+ this.cameraCollisionBlockCache.setWorldBounds(worldConfig.minY, worldConfig.worldHeight)
1046
+ this.queuedChunks.add(`${x},${z}`)
1047
+ const args = [x, z, chunk, isLightUpdate]
1048
+ if (!currentLoadChunkBatch) {
1049
+ // add a setting to use debounce instead
1050
+ currentLoadChunkBatch = {
1051
+ data: [],
1052
+ timeout: setTimeout(() => {
1053
+ for (const args of currentLoadChunkBatch!.data) {
1054
+ this.queuedChunks.delete(`${args[0]},${args[1]}`)
1055
+ this.addColumn(...(args as Parameters<typeof this.addColumn>))
1056
+ }
1057
+ for (const fn of this.queuedFunctions) {
1058
+ fn()
1059
+ }
1060
+ this.queuedFunctions = []
1061
+ currentLoadChunkBatch = null
1062
+ }, this.worldRendererConfig.addChunksBatchWaitTime)
1063
+ }
1017
1064
  }
1018
- }
1019
- currentLoadChunkBatch.data.push(args)
1020
- }, signal)
1065
+ currentLoadChunkBatch.data.push(args)
1066
+ },
1067
+ signal
1068
+ )
1021
1069
  // todo remove and use other architecture instead so data flow is clear
1022
- bindAbortableListener(worldEmitter, 'blockEntities', (blockEntities) => {
1023
- this.blockEntities = blockEntities
1024
- }, signal)
1025
-
1026
- bindAbortableListener(worldEmitter, 'unloadChunk', ({ x, z }) => {
1027
- this.removeColumn(x, z)
1028
- }, signal)
1029
-
1030
- bindAbortableListener(worldEmitter, 'blockUpdate', ({ pos, stateId }) => {
1031
- this.setBlockStateId(new Vec3(pos.x, pos.y, pos.z), stateId)
1032
- }, signal)
1033
-
1034
- bindAbortableListener(worldEmitter, 'chunkPosUpdate', ({ pos }) => {
1035
- this.updateViewerPosition(pos)
1036
- }, signal)
1037
-
1038
- bindAbortableListener(worldEmitter, 'end', () => {
1039
- this.worldStop?.()
1040
- }, signal)
1041
-
1042
- bindAbortableListener(worldEmitter, 'renderDistance', (d) => {
1043
- this.viewDistance = d
1044
- this.chunksLength = d === 0 ? 1 : generateSpiralMatrix(d).length
1045
- this.allChunksFinished = Object.keys(this.finishedChunks).length === this.chunksLength
1046
- this.onRenderDistanceChanged?.(d)
1047
- }, signal)
1048
-
1049
- bindAbortableListener(worldEmitter, 'markAsLoaded', ({ x, z }) => {
1050
- this.markAsLoaded(x, z)
1051
- }, signal)
1052
-
1053
- bindAbortableListener(worldEmitter, 'updateLight', ({ pos }) => {
1054
- this.lightUpdate(pos.x, pos.z)
1055
- }, signal)
1056
-
1057
- bindAbortableListener(worldEmitter, 'onWorldSwitch', () => {
1058
- for (const fn of this.onWorldSwitched) {
1059
- try {
1060
- fn()
1061
- } catch (e) {
1062
- setTimeout(() => {
1063
- console.log('[Renderer Backend] Error in onWorldSwitched:')
1064
- throw e
1065
- }, 0)
1070
+ bindAbortableListener(
1071
+ worldEmitter,
1072
+ 'blockEntities',
1073
+ blockEntities => {
1074
+ this.blockEntities = blockEntities
1075
+ },
1076
+ signal
1077
+ )
1078
+
1079
+ bindAbortableListener(
1080
+ worldEmitter,
1081
+ 'unloadChunk',
1082
+ ({ x, z }) => {
1083
+ this.removeColumn(x, z)
1084
+ },
1085
+ signal
1086
+ )
1087
+
1088
+ bindAbortableListener(
1089
+ worldEmitter,
1090
+ 'blockUpdate',
1091
+ ({ pos, stateId }) => {
1092
+ this.setBlockStateId(new Vec3(pos.x, pos.y, pos.z), stateId)
1093
+ },
1094
+ signal
1095
+ )
1096
+
1097
+ bindAbortableListener(
1098
+ worldEmitter,
1099
+ 'chunkPosUpdate',
1100
+ ({ pos }) => {
1101
+ this.updateViewerPosition(pos)
1102
+ },
1103
+ signal
1104
+ )
1105
+
1106
+ bindAbortableListener(
1107
+ worldEmitter,
1108
+ 'end',
1109
+ () => {
1110
+ this.worldStop?.()
1111
+ },
1112
+ signal
1113
+ )
1114
+
1115
+ bindAbortableListener(
1116
+ worldEmitter,
1117
+ 'renderDistance',
1118
+ d => {
1119
+ this.viewDistance = d
1120
+ this.chunksLength = d === 0 ? 1 : generateSpiralMatrix(d).length
1121
+ this.allChunksFinished = Object.keys(this.finishedChunks).length === this.chunksLength
1122
+ this.onRenderDistanceChanged?.(d)
1123
+ },
1124
+ signal
1125
+ )
1126
+
1127
+ bindAbortableListener(
1128
+ worldEmitter,
1129
+ 'markAsLoaded',
1130
+ ({ x, z }) => {
1131
+ this.markAsLoaded(x, z)
1132
+ },
1133
+ signal
1134
+ )
1135
+
1136
+ bindAbortableListener(
1137
+ worldEmitter,
1138
+ 'updateLight',
1139
+ ({ pos }) => {
1140
+ this.lightUpdate(pos.x, pos.z)
1141
+ },
1142
+ signal
1143
+ )
1144
+
1145
+ bindAbortableListener(
1146
+ worldEmitter,
1147
+ 'onWorldSwitch',
1148
+ () => {
1149
+ this.cameraCollisionBlockCache.clear()
1150
+ for (const fn of this.onWorldSwitched) {
1151
+ try {
1152
+ fn()
1153
+ } catch (e) {
1154
+ setTimeout(() => {
1155
+ console.log('[Renderer Backend] Error in onWorldSwitched:')
1156
+ throw e
1157
+ }, 0)
1158
+ }
1066
1159
  }
1067
- }
1068
- }, signal)
1160
+ },
1161
+ signal
1162
+ )
1069
1163
 
1070
- bindAbortableListener(worldEmitter, 'time', (timeOfDay) => {
1071
- if (!this.worldRendererConfig.dayCycle) {
1072
- return
1073
- }
1074
- this.timeUpdated?.(timeOfDay)
1164
+ bindAbortableListener(
1165
+ worldEmitter,
1166
+ 'time',
1167
+ timeOfDay => {
1168
+ if (!this.worldRendererConfig.dayCycle) {
1169
+ return
1170
+ }
1171
+ this.timeUpdated?.(timeOfDay)
1075
1172
 
1076
- this.timeOfTheDay = timeOfDay
1173
+ this.timeOfTheDay = timeOfDay
1077
1174
 
1078
- const skyLight = (timeOfDay < 0 || timeOfDay > 24_000) ? 15 : calculateSkyLightSimple(timeOfDay)
1079
- if (this.lastMesherSkyLight === skyLight) return
1080
- this.lastMesherSkyLight = skyLight
1081
- if (this.workers.length > 0) {
1082
- this.sendWorkers({ config: { skyLight } } as WorkerSend)
1083
- }
1084
- this.onDayCycleSkyLightChanged?.(skyLight)
1085
- }, signal)
1175
+ const skyLight = timeOfDay < 0 || timeOfDay > 24_000 ? 15 : calculateSkyLightSimple(timeOfDay)
1176
+ if (this.lastMesherSkyLight === skyLight) return
1177
+ this.lastMesherSkyLight = skyLight
1178
+ if (this.workers.length > 0) {
1179
+ this.sendWorkers({ config: { skyLight } } as WorkerSend)
1180
+ }
1181
+ this.onDayCycleSkyLightChanged?.(skyLight)
1182
+ },
1183
+ signal
1184
+ )
1086
1185
 
1087
- bindAbortableListener(worldEmitter, 'biomeUpdate', ({ biome }) => {
1088
- this.biomeUpdated?.(biome)
1089
- }, signal)
1186
+ bindAbortableListener(
1187
+ worldEmitter,
1188
+ 'biomeUpdate',
1189
+ ({ biome }) => {
1190
+ this.biomeUpdated?.(biome)
1191
+ },
1192
+ signal
1193
+ )
1090
1194
 
1091
- bindAbortableListener(worldEmitter, 'biomeReset', () => {
1092
- this.biomeReset?.()
1093
- }, signal)
1195
+ bindAbortableListener(
1196
+ worldEmitter,
1197
+ 'biomeReset',
1198
+ () => {
1199
+ this.biomeReset?.()
1200
+ },
1201
+ signal
1202
+ )
1094
1203
  }
1095
1204
 
1096
1205
  setBlockStateIdInner(pos: Vec3, stateId: number | undefined, needAoRecalculation = true) {
@@ -1118,13 +1227,19 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
1118
1227
  if (!this.worldRendererConfig.wasmMesher) {
1119
1228
  const existing = this.heightmapDebounceTimers.get(chunkKey2)
1120
1229
  if (existing) clearTimeout(existing)
1121
- this.heightmapDebounceTimers.set(chunkKey2, setTimeout(() => {
1122
- this.heightmapDebounceTimers.delete(chunkKey2)
1123
- this.workers[0]?.postMessage({ type: 'getHeightmap', x: chunkCornerX, z: chunkCornerZ })
1124
- }, 100))
1230
+ this.heightmapDebounceTimers.set(
1231
+ chunkKey2,
1232
+ setTimeout(() => {
1233
+ this.heightmapDebounceTimers.delete(chunkKey2)
1234
+ this.workers[0]?.postMessage({ type: 'getHeightmap', x: chunkCornerX, z: chunkCornerZ })
1235
+ }, 100)
1236
+ )
1125
1237
  }
1126
1238
  }
1127
1239
  this.logWorkerWork(`-> blockUpdate ${JSON.stringify({ pos, stateId, customBlockModels })}`)
1240
+ if (stateId !== undefined) {
1241
+ this.cameraCollisionBlockCache.setBlockStateId(pos.x, pos.y, pos.z, stateId)
1242
+ }
1128
1243
  this.setSectionDirty(pos, true, true)
1129
1244
  if (this.neighborChunkUpdates) {
1130
1245
  const CHUNK_SIZE = 16
@@ -1132,7 +1247,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
1132
1247
  if ((pos.x & 15) === 0) this.setSectionDirty(pos.offset(-CHUNK_SIZE, 0, 0), true, true)
1133
1248
  if ((pos.x & 15) === 15) this.setSectionDirty(pos.offset(CHUNK_SIZE, 0, 0), true, true)
1134
1249
  if ((pos.y & (sectionHeight - 1)) === 0) this.setSectionDirty(pos.offset(0, -sectionHeight, 0), true, true)
1135
- if ((pos.y & (sectionHeight - 1)) === (sectionHeight - 1)) this.setSectionDirty(pos.offset(0, sectionHeight, 0), true, true)
1250
+ if ((pos.y & (sectionHeight - 1)) === sectionHeight - 1) this.setSectionDirty(pos.offset(0, sectionHeight, 0), true, true)
1136
1251
  if ((pos.z & 15) === 0) this.setSectionDirty(pos.offset(0, 0, -CHUNK_SIZE), true, true)
1137
1252
  if ((pos.z & 15) === 15) this.setSectionDirty(pos.offset(0, 0, CHUNK_SIZE), true, true)
1138
1253
 
@@ -1153,10 +1268,14 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
1153
1268
  if ((pos.z & 15) === 15 && (pos.y & (sectionHeight - 1)) === 0) this.setSectionDirty(pos.offset(0, -sectionHeight, CHUNK_SIZE), true, true)
1154
1269
 
1155
1270
  // x & z neighbors
1156
- if ((pos.y & (sectionHeight - 1)) === 0 && (pos.x & 15) === 0 && (pos.z & 15) === 0) this.setSectionDirty(pos.offset(-CHUNK_SIZE, -sectionHeight, -CHUNK_SIZE), true, true)
1157
- if ((pos.y & (sectionHeight - 1)) === 0 && (pos.x & 15) === 15 && (pos.z & 15) === 0) this.setSectionDirty(pos.offset(CHUNK_SIZE, -sectionHeight, -CHUNK_SIZE), true, true)
1158
- if ((pos.y & (sectionHeight - 1)) === 0 && (pos.x & 15) === 0 && (pos.z & 15) === 15) this.setSectionDirty(pos.offset(-CHUNK_SIZE, -sectionHeight, CHUNK_SIZE), true, true)
1159
- if ((pos.y & (sectionHeight - 1)) === 0 && (pos.x & 15) === 15 && (pos.z & 15) === 15) this.setSectionDirty(pos.offset(CHUNK_SIZE, -sectionHeight, CHUNK_SIZE), true, true)
1271
+ if ((pos.y & (sectionHeight - 1)) === 0 && (pos.x & 15) === 0 && (pos.z & 15) === 0)
1272
+ this.setSectionDirty(pos.offset(-CHUNK_SIZE, -sectionHeight, -CHUNK_SIZE), true, true)
1273
+ if ((pos.y & (sectionHeight - 1)) === 0 && (pos.x & 15) === 15 && (pos.z & 15) === 0)
1274
+ this.setSectionDirty(pos.offset(CHUNK_SIZE, -sectionHeight, -CHUNK_SIZE), true, true)
1275
+ if ((pos.y & (sectionHeight - 1)) === 0 && (pos.x & 15) === 0 && (pos.z & 15) === 15)
1276
+ this.setSectionDirty(pos.offset(-CHUNK_SIZE, -sectionHeight, CHUNK_SIZE), true, true)
1277
+ if ((pos.y & (sectionHeight - 1)) === 0 && (pos.x & 15) === 15 && (pos.z & 15) === 15)
1278
+ this.setSectionDirty(pos.offset(CHUNK_SIZE, -sectionHeight, CHUNK_SIZE), true, true)
1160
1279
  }
1161
1280
  }
1162
1281
  }
@@ -1211,13 +1330,15 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
1211
1330
  async debugGetWorkerCustomBlockModel(pos: Vec3) {
1212
1331
  const data = [] as Array<Promise<string>>
1213
1332
  for (const worker of this.workers) {
1214
- data.push(new Promise((resolve) => {
1215
- worker.addEventListener('message', (e) => {
1216
- if (e.data.type === 'customBlockModel') {
1217
- resolve(e.data.customBlockModel)
1218
- }
1333
+ data.push(
1334
+ new Promise(resolve => {
1335
+ worker.addEventListener('message', e => {
1336
+ if (e.data.type === 'customBlockModel') {
1337
+ resolve(e.data.customBlockModel)
1338
+ }
1339
+ })
1219
1340
  })
1220
- }))
1341
+ )
1221
1342
  worker.postMessage({
1222
1343
  type: 'getCustomBlockModel',
1223
1344
  pos
@@ -1226,7 +1347,8 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
1226
1347
  return Promise.all(data)
1227
1348
  }
1228
1349
 
1229
- setSectionDirty(pos: Vec3, value = true, useChangeWorker = false) { // value false is used for unloading chunks
1350
+ setSectionDirty(pos: Vec3, value = true, useChangeWorker = false) {
1351
+ // value false is used for unloading chunks
1230
1352
  if (!this.forceCallFromMesherReplayer && this.mesherLogReader) return
1231
1353
 
1232
1354
  if (this.viewDistance === -1) throw new Error('viewDistance not set')
@@ -1254,30 +1376,36 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
1254
1376
 
1255
1377
  // Schedule trailing dispatch after throttle window
1256
1378
  if (!this.sectionDirtyTimers.has(key)) {
1257
- this.sectionDirtyTimers.set(key, setTimeout(() => {
1258
- const args = this.sectionDirtyPendingArgs.get(key)
1259
- this.sectionDirtyCount.delete(key)
1260
- this.sectionDirtyTimers.delete(key)
1261
- this.sectionDirtyPendingArgs.delete(key)
1262
- if (args) {
1263
- this._dispatchDirtyImmediate(args.pos, args.value, args.useChangeWorker)
1264
- }
1265
- }, WorldRendererCommon.GEOMETRY_THROTTLE_DELAY))
1379
+ this.sectionDirtyTimers.set(
1380
+ key,
1381
+ setTimeout(() => {
1382
+ const args = this.sectionDirtyPendingArgs.get(key)
1383
+ this.sectionDirtyCount.delete(key)
1384
+ this.sectionDirtyTimers.delete(key)
1385
+ this.sectionDirtyPendingArgs.delete(key)
1386
+ if (args) {
1387
+ this._dispatchDirtyImmediate(args.pos, args.value, args.useChangeWorker)
1388
+ }
1389
+ }, WorldRendererCommon.GEOMETRY_THROTTLE_DELAY)
1390
+ )
1266
1391
  }
1267
1392
  } else {
1268
1393
  // Subsequent requests — throttle: store latest args, existing timer will dispatch
1269
1394
  this.sectionDirtyPendingArgs.set(key, { pos, value, useChangeWorker })
1270
1395
 
1271
1396
  if (!this.sectionDirtyTimers.has(key)) {
1272
- this.sectionDirtyTimers.set(key, setTimeout(() => {
1273
- const args = this.sectionDirtyPendingArgs.get(key)
1274
- this.sectionDirtyCount.delete(key)
1275
- this.sectionDirtyTimers.delete(key)
1276
- this.sectionDirtyPendingArgs.delete(key)
1277
- if (args) {
1278
- this._dispatchDirtyImmediate(args.pos, args.value, args.useChangeWorker)
1279
- }
1280
- }, WorldRendererCommon.GEOMETRY_THROTTLE_DELAY))
1397
+ this.sectionDirtyTimers.set(
1398
+ key,
1399
+ setTimeout(() => {
1400
+ const args = this.sectionDirtyPendingArgs.get(key)
1401
+ this.sectionDirtyCount.delete(key)
1402
+ this.sectionDirtyTimers.delete(key)
1403
+ this.sectionDirtyPendingArgs.delete(key)
1404
+ if (args) {
1405
+ this._dispatchDirtyImmediate(args.pos, args.value, args.useChangeWorker)
1406
+ }
1407
+ }, WorldRendererCommon.GEOMETRY_THROTTLE_DELAY)
1408
+ )
1281
1409
  }
1282
1410
  }
1283
1411
  }
@@ -1302,7 +1430,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
1302
1430
  y: pos.y,
1303
1431
  z: pos.z,
1304
1432
  value,
1305
- config: this.getMesherConfig(),
1433
+ config: this.getMesherConfig()
1306
1434
  })
1307
1435
  } else {
1308
1436
  this.toWorkerMessagesQueue[hash] ??= []
@@ -1312,7 +1440,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
1312
1440
  y: pos.y,
1313
1441
  z: pos.z,
1314
1442
  value,
1315
- config: this.getMesherConfig(),
1443
+ config: this.getMesherConfig()
1316
1444
  })
1317
1445
  this.dispatchMessages()
1318
1446
  }
@@ -1328,7 +1456,9 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
1328
1456
  const messages = this.toWorkerMessagesQueue[workerIndex]
1329
1457
  worker.postMessage(messages)
1330
1458
  for (const message of messages) {
1331
- this.logWorkerWork(`-> ${workerIndex} dispatchMessages ${message.type} ${JSON.stringify({ x: message.x, y: message.y, z: message.z, value: message.value })}`)
1459
+ this.logWorkerWork(
1460
+ `-> ${workerIndex} dispatchMessages ${message.type} ${JSON.stringify({ x: message.x, y: message.y, z: message.z, value: message.value })}`
1461
+ )
1332
1462
  }
1333
1463
  }
1334
1464
  this.toWorkerMessagesQueue = {}
@@ -1436,7 +1566,10 @@ export const initMesherWorker = (onGotMessage: (data: any) => void, workerName =
1436
1566
  worker.onmessage = ({ data }) => {
1437
1567
  onGotMessage(data)
1438
1568
  }
1439
- if (worker.on) worker.on('message', (data) => { worker.onmessage({ data }) })
1569
+ if (worker.on)
1570
+ worker.on('message', data => {
1571
+ worker.onmessage({ data })
1572
+ })
1440
1573
  return worker
1441
1574
  }
1442
1575
 
@@ -1467,19 +1600,24 @@ export const meshersSendMcDataAwait = (
1467
1600
  mcDataFull: IndexedData,
1468
1601
  timeoutMs = 10_000
1469
1602
  ): Promise<void> => {
1470
- return Promise.all(workers.map(worker => new Promise<void>((resolve, reject) => {
1471
- const timeout = setTimeout(() => {
1472
- worker.removeEventListener('message', handler as EventListener)
1473
- reject(new Error(`mcData transfer timeout (${timeoutMs}ms)`))
1474
- }, timeoutMs)
1475
- const handler = ({ data }: MessageEvent) => {
1476
- if (data?.type === 'mcDataApplied') {
1477
- clearTimeout(timeout)
1478
- worker.removeEventListener('message', handler as EventListener)
1479
- resolve()
1480
- }
1481
- }
1482
- worker.addEventListener('message', handler as EventListener)
1483
- meshersSendMcData([worker], version, mcDataKeys, mcDataFull)
1484
- }))).then(() => undefined)
1603
+ return Promise.all(
1604
+ workers.map(
1605
+ worker =>
1606
+ new Promise<void>((resolve, reject) => {
1607
+ const timeout = setTimeout(() => {
1608
+ worker.removeEventListener('message', handler as EventListener)
1609
+ reject(new Error(`mcData transfer timeout (${timeoutMs}ms)`))
1610
+ }, timeoutMs)
1611
+ const handler = ({ data }: MessageEvent) => {
1612
+ if (data?.type === 'mcDataApplied') {
1613
+ clearTimeout(timeout)
1614
+ worker.removeEventListener('message', handler as EventListener)
1615
+ resolve()
1616
+ }
1617
+ }
1618
+ worker.addEventListener('message', handler as EventListener)
1619
+ meshersSendMcData([worker], version, mcDataKeys, mcDataFull)
1620
+ })
1621
+ )
1622
+ ).then(() => undefined)
1485
1623
  }