minecraft-renderer 0.1.71 → 0.1.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +3 -3
  2. package/dist/mesher.js +81 -81
  3. package/dist/mesher.js.map +3 -3
  4. package/dist/mesherWasm.js +1183 -943
  5. package/dist/minecraft-renderer.js +250 -79
  6. package/dist/minecraft-renderer.js.meta.json +1 -1
  7. package/dist/threeWorker.js +1732 -1001
  8. package/package.json +3 -3
  9. package/src/graphicsBackend/rendererDefaultOptions.ts +5 -10
  10. package/src/graphicsBackend/rendererOptionsSync.ts +1 -1
  11. package/src/lib/bakeLegacyLight.ts +17 -0
  12. package/src/lib/blockEntityLightRegistry.test.ts +18 -0
  13. package/src/lib/blockEntityLightRegistry.ts +75 -0
  14. package/src/lib/blockEntityLighting.test.ts +30 -0
  15. package/src/lib/blockEntityLighting.ts +53 -0
  16. package/src/lib/worldrendererCommon.reconfigure.test.ts +202 -0
  17. package/src/lib/worldrendererCommon.ts +152 -22
  18. package/src/mesher-shared/blockEntityMetadata.test.ts +33 -0
  19. package/src/mesher-shared/blockEntityMetadata.ts +19 -3
  20. package/src/mesher-shared/exportedGeometryTypes.ts +11 -0
  21. package/src/mesher-shared/models.ts +161 -92
  22. package/src/mesher-shared/shared.ts +15 -4
  23. package/src/mesher-shared/tests/liquidQuadInvariant.test.ts +40 -0
  24. package/src/mesher-shared/world.ts +12 -0
  25. package/src/mesher-shared/worldLighting.test.ts +54 -0
  26. package/src/playground/baseScene.ts +1 -1
  27. package/src/three/bannerRenderer.ts +10 -3
  28. package/src/three/chunkMeshManager.ts +663 -69
  29. package/src/three/cubeDrawSpans.ts +74 -0
  30. package/src/three/cubeMultiDraw.ts +119 -0
  31. package/src/three/documentRenderer.ts +0 -2
  32. package/src/three/entities.ts +5 -6
  33. package/src/three/entity/EntityMesh.ts +7 -5
  34. package/src/three/entity/gltfAnimationUtils.ts +5 -3
  35. package/src/three/globalBlockBuffer.ts +208 -12
  36. package/src/three/globalLegacyBuffer.ts +701 -0
  37. package/src/three/graphicsBackendOffThread.ts +16 -1
  38. package/src/three/itemMesh.ts +5 -2
  39. package/src/three/legacySectionCull.ts +85 -0
  40. package/src/three/modules/sciFiWorldReveal.ts +347 -703
  41. package/src/three/modules/starfield.ts +3 -2
  42. package/src/three/sectionRaycastAabb.ts +25 -0
  43. package/src/three/shaders/cubeBlockShader.ts +80 -17
  44. package/src/three/shaders/legacyBlockShader.ts +292 -0
  45. package/src/three/skyboxRenderer.ts +1 -1
  46. package/src/three/tests/chunkMeshManagerLegacy.test.ts +286 -0
  47. package/src/three/tests/cubeDrawSpans.test.ts +73 -0
  48. package/src/three/tests/globalLegacyBuffer.test.ts +360 -0
  49. package/src/three/tests/legacySectionCull.test.ts +80 -0
  50. package/src/three/tests/signTextureCache.test.ts +83 -0
  51. package/src/three/threeJsMedia.ts +2 -2
  52. package/src/three/waypointSprite.ts +2 -2
  53. package/src/three/world/cursorBlock.ts +1 -0
  54. package/src/three/world/vr.ts +2 -2
  55. package/src/three/worldGeometryExport.ts +83 -26
  56. package/src/three/worldRendererThree.ts +94 -25
  57. package/src/wasm-mesher/bridge/render-from-wasm.ts +214 -72
  58. package/src/wasm-mesher/bridge/shaderCubeBridge.ts +18 -6
  59. package/src/wasm-mesher/runtime-build/wasm_mesher_bg.wasm +0 -0
  60. package/src/wasm-mesher/tests/sectionRaycastAabb.test.ts +20 -0
  61. package/src/wasm-mesher/tests/shaderCubeInstances.test.ts +67 -5
  62. package/src/wasm-mesher/worker/mesherWasm.ts +70 -14
  63. package/src/wasm-mesher/worker/mesherWasmLightDirty.test.ts +11 -0
  64. package/src/wasm-mesher/worker/mesherWasmLightDirty.ts +15 -0
  65. package/src/worldView/worldView.ts +11 -0
@@ -33,6 +33,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
33
33
  worldReadyResolvers = Promise.withResolvers<void>()
34
34
  worldReadyPromise = this.worldReadyResolvers.promise
35
35
  timeOfTheDay = 0
36
+ lastMesherSkyLight = 15
36
37
  worldSizeParams = { minY: 0, worldHeight: 256 }
37
38
  reactiveDebugParams = proxy({
38
39
  stopRendering: false,
@@ -127,6 +128,12 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
127
128
  debugStopGeometryUpdate = false
128
129
 
129
130
  protocolCustomBlocks = new Map<string, CustomBlockModels>()
131
+ private mesherPoolSnapshot = {
132
+ mesherWorkers: -1,
133
+ wasmMesher: false,
134
+ dedicatedChangeWorker: false,
135
+ }
136
+ private mesherReconfigureQueue: Promise<void> = Promise.resolve()
130
137
  private heightmapDebounceTimers = new Map<string, ReturnType<typeof setTimeout>>()
131
138
 
132
139
  // Geometry throttle: first dirty per section is instant, subsequent within window are grouped
@@ -272,6 +279,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
272
279
 
273
280
  this.watchReactivePlayerState()
274
281
  this.watchReactiveConfig()
282
+ this.watchMesherPoolConfig()
275
283
  this.worldReadyResolvers.resolve()
276
284
  }
277
285
 
@@ -309,18 +317,127 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
309
317
  }
310
318
  }
311
319
 
320
+ private getMesherWorkerScript(): 'wasm' | 'legacy' {
321
+ return this.worldRendererConfig.wasmMesher ? 'wasm' : 'legacy'
322
+ }
323
+
324
+ private createMesherWorker() {
325
+ 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')
334
+ }
335
+
312
336
  initWorkers(numWorkers = this.worldRendererConfig.mesherWorkers) {
313
- // init workers
314
- for (let i = 0; i < numWorkers + 0; i++) {
315
- const worker = initMesherWorker((data) => {
316
- if (Array.isArray(data)) {
317
- this.messageQueue.push(...data)
318
- } else {
319
- this.messageQueue.push(data)
320
- }
321
- void this.processMessageQueue('worker')
322
- }, this.worldRendererConfig.wasmMesher ? 'mesherWasm.js' : 'mesher.js')
323
- this.workers.push(worker)
337
+ for (let i = 0; i < numWorkers; i++) {
338
+ this.workers.push(this.createMesherWorker())
339
+ }
340
+ }
341
+
342
+ private syncMesherPoolSnapshot() {
343
+ this.mesherPoolSnapshot = {
344
+ mesherWorkers: this.worldRendererConfig.mesherWorkers,
345
+ wasmMesher: this.worldRendererConfig.wasmMesher,
346
+ dedicatedChangeWorker: this.worldRendererConfig.dedicatedChangeWorker,
347
+ }
348
+ }
349
+
350
+ private watchMesherPoolConfig() {
351
+ this.syncMesherPoolSnapshot()
352
+
353
+ const tryReconfigure = () => {
354
+ const cfg = this.worldRendererConfig
355
+ const snap = this.mesherPoolSnapshot
356
+ if (
357
+ cfg.mesherWorkers === snap.mesherWorkers &&
358
+ cfg.wasmMesher === snap.wasmMesher &&
359
+ cfg.dedicatedChangeWorker === snap.dedicatedChangeWorker
360
+ ) {
361
+ return
362
+ }
363
+ this.syncMesherPoolSnapshot()
364
+ this.enqueueMesherWorkersReconfigure()
365
+ }
366
+
367
+ for (const key of ['mesherWorkers', 'wasmMesher', 'dedicatedChangeWorker'] as const) {
368
+ this.valtioUnsubs.push(
369
+ this.onReactiveConfigUpdated(key, tryReconfigure, false)
370
+ )
371
+ }
372
+ }
373
+
374
+ private enqueueMesherWorkersReconfigure() {
375
+ this.mesherReconfigureQueue = this.mesherReconfigureQueue
376
+ .then(() => this.reconfigureMesherWorkers())
377
+ .catch((err) => {
378
+ console.error('[Mesher] Failed to reconfigure workers:', err)
379
+ })
380
+ }
381
+ private clearMesherPendingState() {
382
+ this.sectionsWaiting.clear()
383
+ this.toWorkerMessagesQueue = {}
384
+ this.queueAwaited = false
385
+ this.messageQueue = []
386
+ this.isProcessingQueue = false
387
+ for (const timer of this.sectionDirtyTimers.values()) {
388
+ clearTimeout(timer)
389
+ }
390
+ this.sectionDirtyTimers.clear()
391
+ this.sectionDirtyCount.clear()
392
+ this.sectionDirtyPendingArgs.clear()
393
+ this.reactiveState.world.mesherWork = false
394
+ }
395
+
396
+ private terminateAllMesherWorkers() {
397
+ for (const worker of this.workers) {
398
+ worker.terminate()
399
+ }
400
+ this.workers = []
401
+ }
402
+
403
+ private async bootstrapMesherWorkers() {
404
+ if (this.workers.length === 0) return
405
+
406
+ this.sendMesherMcData()
407
+ await this.updateAssetsData()
408
+ this.logWorkerWork('# mesher workers bootstrapped')
409
+ }
410
+
411
+ async reconfigureMesherWorkers() {
412
+ if (!this.active) return
413
+
414
+ this.clearMesherPendingState()
415
+ this.terminateAllMesherWorkers()
416
+ this.initWorkers()
417
+ await this.bootstrapMesherWorkers()
418
+ if (!this.active) return
419
+
420
+ await this.requestLoadedChunksReload()
421
+ }
422
+
423
+ private async requestLoadedChunksReload() {
424
+ try {
425
+ const worldView = this.displayOptions.worldView as {
426
+ reloadLoadedChunks?: () => void | Promise<void>
427
+ }
428
+
429
+ if (typeof worldView.reloadLoadedChunks === 'function') {
430
+ await worldView.reloadLoadedChunks()
431
+ return
432
+ }
433
+
434
+ const workerScope = globalThis as typeof globalThis & { WorkerGlobalScope?: typeof WorkerGlobalScope }
435
+ if (typeof workerScope.WorkerGlobalScope !== 'undefined' && globalThis instanceof workerScope.WorkerGlobalScope) {
436
+ // eslint-disable-next-line no-restricted-globals
437
+ self.postMessage({ type: 'reloadLoadedChunks' })
438
+ }
439
+ } catch (err) {
440
+ console.error('[Mesher] Failed to reload chunks after worker reconfigure:', err)
324
441
  }
325
442
  }
326
443
 
@@ -331,13 +448,18 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
331
448
  return subscribeKey(this.playerStateReactive, key, callback)
332
449
  }
333
450
 
334
- onReactiveConfigUpdated<T extends keyof typeof this.worldRendererConfig>(key: T, callback: (value: typeof this.worldRendererConfig[T]) => void) {
335
- callback(this.worldRendererConfig[key])
451
+ onReactiveConfigUpdated<T extends keyof typeof this.worldRendererConfig>(
452
+ key: T,
453
+ callback: (value: typeof this.worldRendererConfig[T]) => void,
454
+ initial = true
455
+ ) {
456
+ if (initial) {
457
+ callback(this.worldRendererConfig[key])
458
+ }
336
459
  if ((key as any) === '*') {
337
- subscribe(this.worldRendererConfig, callback as any)
338
- } else {
339
- subscribeKey(this.worldRendererConfig, key, callback)
460
+ return subscribe(this.worldRendererConfig, callback as any)
340
461
  }
462
+ return subscribeKey(this.worldRendererConfig, key, callback)
341
463
  }
342
464
 
343
465
  onReactiveDebugUpdated<T extends keyof typeof this.reactiveDebugParams>(key: T, callback: (value: typeof this.reactiveDebugParams[T]) => void) {
@@ -574,6 +696,9 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
574
696
 
575
697
  timeUpdated?(newTime: number): void
576
698
 
699
+ /** Called when day-cycle sky-light bucket changes; Three.js overrides to remesh. */
700
+ protected onDayCycleSkyLightChanged?(_skyLight: number): void
701
+
577
702
  biomeUpdated?(biome: any): void
578
703
 
579
704
  biomeReset?(): void
@@ -637,6 +762,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
637
762
 
638
763
  this.initWorkers()
639
764
  this.active = true
765
+ this.syncMesherPoolSnapshot()
640
766
 
641
767
  this.sendMesherMcData()
642
768
  }
@@ -942,16 +1068,20 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
942
1068
  }, signal)
943
1069
 
944
1070
  bindAbortableListener(worldEmitter, 'time', (timeOfDay) => {
945
- if (!this.worldRendererConfig.dayCycle) return
1071
+ if (!this.worldRendererConfig.dayCycle) {
1072
+ return
1073
+ }
946
1074
  this.timeUpdated?.(timeOfDay)
947
1075
 
948
1076
  this.timeOfTheDay = timeOfDay
949
1077
 
950
- // if (this.worldRendererConfig.skyLight === skyLight) return
951
- // this.worldRendererConfig.skyLight = skyLight
952
- // if (this instanceof WorldRendererThree) {
953
- // (this).rerenderAllChunks?.()
954
- // }
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)
955
1085
  }, signal)
956
1086
 
957
1087
  bindAbortableListener(worldEmitter, 'biomeUpdate', ({ biome }) => {
@@ -0,0 +1,33 @@
1
+ //@ts-nocheck
2
+ import { describe, expect, it } from 'vitest'
3
+ import { Vec3 } from 'vec3'
4
+ import { collectBlockEntityMetadata } from './blockEntityMetadata'
5
+
6
+ describe('collectBlockEntityMetadata', () => {
7
+ it('stores channel light norms on banner metadata', () => {
8
+ const target = { signs: {}, heads: {}, banners: {} }
9
+ const block = {
10
+ name: 'pink_banner',
11
+ getProperties: () => ({ rotation: 0 }),
12
+ }
13
+ collectBlockEntityMetadata(
14
+ block,
15
+ 1, 2, 3,
16
+ target,
17
+ {},
18
+ {
19
+ getChannelLightNorm: (pos: Vec3) => {
20
+ expect(pos).toBeInstanceOf(Vec3)
21
+ return { block: 0.4, sky: 0.8 }
22
+ },
23
+ },
24
+ )
25
+ expect(target.banners['1,2,3']).toEqual({
26
+ isWall: false,
27
+ blockName: 'pink_banner',
28
+ rotation: 0,
29
+ blockLightNorm: 0.4,
30
+ skyLightNorm: 0.8,
31
+ })
32
+ })
33
+ })
@@ -1,7 +1,15 @@
1
1
  //@ts-nocheck
2
+ import { Vec3 } from 'vec3'
3
+
2
4
  export interface SignMeta { isWall: boolean; isHanging: boolean; rotation: number }
3
5
  export interface HeadMeta { isWall: boolean; rotation: number }
4
- export interface BannerMeta { isWall: boolean; blockName: string; rotation: number }
6
+ export interface BannerMeta {
7
+ isWall: boolean
8
+ blockName: string
9
+ rotation: number
10
+ blockLightNorm: number
11
+ skyLightNorm: number
12
+ }
5
13
 
6
14
  export interface BlockEntityMetadataTarget {
7
15
  signs: Record<string, SignMeta>
@@ -15,11 +23,16 @@ export interface BlockEntityMetadataOptions {
15
23
 
16
24
  type BlockLike = { name: string; getProperties(): any }
17
25
 
26
+ type LightSampler = {
27
+ getChannelLightNorm(pos: Vec3): { block: number, sky: number }
28
+ }
29
+
18
30
  export function collectBlockEntityMetadata(
19
31
  block: BlockLike,
20
32
  x: number, y: number, z: number,
21
33
  target: BlockEntityMetadataTarget,
22
- options: BlockEntityMetadataOptions
34
+ options: BlockEntityMetadataOptions,
35
+ world?: LightSampler,
23
36
  ): void {
24
37
  if ((block.name.includes('_sign') || block.name === 'sign') && !options.disableBlockEntityTextures) {
25
38
  const key = `${x},${y},${z}`
@@ -61,10 +74,13 @@ export function collectBlockEntityMetadata(
61
74
  'east': 3
62
75
  }
63
76
  const isWall = block.name.endsWith('_wall_banner')
77
+ const light = world?.getChannelLightNorm(new Vec3(x, y, z)) ?? { block: 0, sky: 1 }
64
78
  target.banners[key] = {
65
79
  isWall,
66
80
  blockName: block.name, // Pass block name for base color extraction
67
- rotation: isWall ? facingRotationMap[props.facing] : (props.rotation === undefined ? 0 : +props.rotation)
81
+ rotation: isWall ? facingRotationMap[props.facing] : (props.rotation === undefined ? 0 : +props.rotation),
82
+ blockLightNorm: light.block,
83
+ skyLightNorm: light.sky,
68
84
  }
69
85
  }
70
86
  }
@@ -8,6 +8,17 @@ export interface ExportedSection {
8
8
  positions: number[]
9
9
  normals: number[]
10
10
  colors: number[]
11
+ skyLights: number[]
12
+ blockLights: number[]
13
+ uvs: number[]
14
+ indices: number[]
15
+ }
16
+ blendGeometry?: {
17
+ positions: number[]
18
+ normals: number[]
19
+ colors: number[]
20
+ skyLights: number[]
21
+ blockLights: number[]
11
22
  uvs: number[]
12
23
  indices: number[]
13
24
  }