minecraft-renderer 0.1.77 → 0.1.79

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minecraft-renderer",
3
- "version": "0.1.77",
3
+ "version": "0.1.79",
4
4
  "description": "The most Modular Minecraft world renderer with Three.js WebGL backend",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -4,7 +4,7 @@ import * as nbt from 'prismarine-nbt'
4
4
  import { Vec3 } from 'vec3'
5
5
  import { MesherGeometryOutput } from '../mesher-shared/shared'
6
6
  import { getShaderCubeResources, SHADER_CUBES_WORDS_PER_FACE } from '../wasm-mesher/bridge/shaderCubeBridge'
7
- import { createCubeBlockMaterial, computeSectionOriginRel, setCubeSkyLevel, setCubeLightmapParams, type BlockLightmapParams } from './shaders/cubeBlockShader'
7
+ import { createCubeBlockMaterial, computeSectionOriginRel, setCubeSkyLevel, setCubeShadingTheme, setCubeLightmapParams, type BlockLightmapParams } from './shaders/cubeBlockShader'
8
8
  import { computeCameraRelativeUniforms, createGlobalLegacyBlendMaterial, createGlobalLegacyBlockMaterial, createLegacyBlockMaterial, setLegacyCameraOrigin, setLegacySkyLevel, setLegacyLightmapParams, type RenderOrigin } from './shaders/legacyBlockShader'
9
9
  import { LEGACY_SECTION_HALF_EXTENT, sectionIntersectsFrustum, setupLegacySectionMatrix, updateLegacySectionCullState } from './legacySectionCull'
10
10
  import { createShaderCubeMesh, disposeShaderCubeMesh } from './shaderCubeMesh'
@@ -140,6 +140,9 @@ export class ChunkMeshManager {
140
140
  private readonly _legacyCullBoxMin = new THREE.Vector3()
141
141
  private readonly _legacyCullBoxMax = new THREE.Vector3()
142
142
  private readonly _visibleSectionSpans: Array<{ key: string, distSq: number }> = []
143
+ /** 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 }>()
145
+ private _lastCullFingerprint = ''
143
146
  /** Drives per-frame cull + span rebuild; cleared after updateSectionCullAndSort. */
144
147
  cullDirty = true
145
148
  private readonly _lastCullCamPos = new THREE.Vector3()
@@ -266,6 +269,11 @@ export class ChunkMeshManager {
266
269
  this.blockEntityLightRegistry.setSkyLevel(value)
267
270
  }
268
271
 
272
+ setShadingTheme (theme: 'vanilla' | 'high-contrast', cardinalLight: string): void {
273
+ const cube = this.cubeShaderMaterial ?? (this.isShaderCubesGpuEnabled() ? this.getCubeShaderMaterial() : null)
274
+ if (cube) setCubeShadingTheme(cube, theme, cardinalLight)
275
+ }
276
+
269
277
  /** Vanilla-like lightmap curve params (live tuning via window.setBlockLightmap). */
270
278
  setBlockLightmapParams (params: BlockLightmapParams): void {
271
279
  const cube = this.cubeShaderMaterial ?? (this.isShaderCubesGpuEnabled() ? this.getCubeShaderMaterial() : null)
@@ -379,6 +387,45 @@ export class ChunkMeshManager {
379
387
  return this.activeSections.has(sectionKey)
380
388
  }
381
389
 
390
+ private registerLegacyCullSection (sectionKey: string, worldX: number, worldY: number, worldZ: number): void {
391
+ this.legacyCullSections.set(sectionKey, { worldX, worldY, worldZ })
392
+ }
393
+
394
+ private maybeUnregisterLegacyCullSection (sectionKey: string): void {
395
+ const inOpaque = this.globalLegacyBuffer?.hasSection(sectionKey) ?? false
396
+ const inBlend = this.globalLegacyBlendBuffer?.hasSection(sectionKey) ?? false
397
+ if (!inOpaque && !inBlend) {
398
+ this.legacyCullSections.delete(sectionKey)
399
+ }
400
+ }
401
+
402
+ private updatePooledLegacyCullState (
403
+ cameraWorldX: number,
404
+ cameraWorldY: number,
405
+ cameraWorldZ: number,
406
+ ): void {
407
+ for (const poolEntry of this.activeSections.values()) {
408
+ const sectionKey = poolEntry.sectionKey
409
+ if (!sectionKey) continue
410
+ const sectionObject = this.sectionObjects[sectionKey]
411
+ if (!sectionObject) continue
412
+
413
+ updateLegacySectionCullState(
414
+ poolEntry.mesh,
415
+ sectionObject.worldX ?? 0,
416
+ sectionObject.worldY ?? 0,
417
+ sectionObject.worldZ ?? 0,
418
+ cameraWorldX,
419
+ cameraWorldY,
420
+ cameraWorldZ,
421
+ this._legacyCullFrustum,
422
+ this._legacyCullBox,
423
+ this._legacyCullBoxMin,
424
+ this._legacyCullBoxMax,
425
+ )
426
+ }
427
+ }
428
+
382
429
  /**
383
430
  * Shared section visibility + span groups for global legacy and cube buffers.
384
431
  */
@@ -389,8 +436,9 @@ export class ChunkMeshManager {
389
436
  const visible = this._visibleSectionSpans
390
437
  visible.length = 0
391
438
 
392
- for (const [sectionKey, sectionObject] of Object.entries(this.sectionObjects)) {
393
- if (sectionObject.worldX === undefined || !sectionObject.visible) continue
439
+ for (const [sectionKey] of this.legacyCullSections) {
440
+ const sectionObject = this.sectionObjects[sectionKey]
441
+ if (!sectionObject?.visible || sectionObject.worldX === undefined) continue
394
442
  const { visible: inFrustum, distSq } = sectionIntersectsFrustum(
395
443
  sectionObject.worldX,
396
444
  sectionObject.worldY ?? 0,
@@ -408,13 +456,27 @@ export class ChunkMeshManager {
408
456
  }
409
457
  }
410
458
 
411
- this.globalLegacyBuffer?.updateDrawSpans(visible, 'opaque')
412
- this.globalLegacyBlendBuffer?.updateDrawSpans(visible, 'sortedBlend')
413
-
459
+ const opaqueBuf = this.globalLegacyBuffer
460
+ const blendBuf = this.globalLegacyBlendBuffer
414
461
  const gb = this.globalBlockBuffer
462
+
463
+ const opaqueKeys: string[] = []
464
+ for (const entry of visible) {
465
+ if (opaqueBuf?.hasSection(entry.key)) opaqueKeys.push(entry.key)
466
+ }
467
+ opaqueKeys.sort()
468
+
469
+ const blendVisible = visible.filter(v => blendBuf?.hasSection(v.key))
470
+ blendVisible.sort((a, b) => b.distSq - a.distSq)
471
+ const blendKeys = blendVisible.map(v => v.key)
472
+
473
+ const cubeVisibleKeys: string[] = []
474
+ const visibleSlots: Array<{ start: number, count: number }> = []
415
475
  if (gb) {
416
- const visibleSlots: Array<{ start: number, count: number }> = []
417
476
  gb.forEachSectionSlot((key, slot) => {
477
+ const sectionObject = this.sectionObjects[key]
478
+ // Keep in sync with legacy gate (line 440).
479
+ if (!sectionObject?.visible) return
418
480
  const entry = this.shaderSectionRaycastBoxes.get(key)
419
481
  if (!entry) {
420
482
  return
@@ -434,35 +496,39 @@ export class ChunkMeshManager {
434
496
  if (!inFrustum) {
435
497
  return
436
498
  }
499
+ cubeVisibleKeys.push(key)
437
500
  const drawStart = gb.getSectionDrawStart(key)
438
501
  if (drawStart !== undefined) {
439
502
  visibleSlots.push({ start: drawStart, count: slot.count })
440
503
  }
441
504
  })
442
- const spans = buildVisibleCubeSpans(visibleSlots, gb.getHighWatermark())
443
- gb.setVisibleSpans(spans)
444
505
  }
506
+ cubeVisibleKeys.sort()
445
507
 
446
- for (const poolEntry of this.activeSections.values()) {
447
- const sectionKey = poolEntry.sectionKey
448
- if (!sectionKey) continue
449
- const sectionObject = this.sectionObjects[sectionKey]
450
- if (!sectionObject) continue
508
+ const fingerprint = [
509
+ opaqueKeys.join(','),
510
+ blendKeys.join(','),
511
+ cubeVisibleKeys.join(','),
512
+ opaqueBuf?.getLayoutVersion() ?? 0,
513
+ blendBuf?.getLayoutVersion() ?? 0,
514
+ gb?.getLayoutVersion() ?? 0,
515
+ ].join('|')
451
516
 
452
- updateLegacySectionCullState(
453
- poolEntry.mesh,
454
- sectionObject.worldX ?? 0,
455
- sectionObject.worldY ?? 0,
456
- sectionObject.worldZ ?? 0,
457
- cameraWorldX,
458
- cameraWorldY,
459
- cameraWorldZ,
460
- this._legacyCullFrustum,
461
- this._legacyCullBox,
462
- this._legacyCullBoxMin,
463
- this._legacyCullBoxMax,
464
- )
517
+ if (fingerprint === this._lastCullFingerprint) {
518
+ this.updatePooledLegacyCullState(cameraWorldX, cameraWorldY, cameraWorldZ)
519
+ return
520
+ }
521
+ this._lastCullFingerprint = fingerprint
522
+
523
+ opaqueBuf?.updateDrawSpans(visible, 'opaque')
524
+ blendBuf?.updateDrawSpans(visible, 'sortedBlend')
525
+
526
+ if (gb) {
527
+ const spans = buildVisibleCubeSpans(visibleSlots, gb.getHighWatermark())
528
+ gb.setVisibleSpans(spans)
465
529
  }
530
+
531
+ this.updatePooledLegacyCullState(cameraWorldX, cameraWorldY, cameraWorldZ)
466
532
  }
467
533
 
468
534
  markCullDirty (): void {
@@ -531,6 +597,8 @@ export class ChunkMeshManager {
531
597
  if (!this.cubeShaderMaterial) {
532
598
  this.cubeShaderMaterial = createCubeBlockMaterial()
533
599
  this.syncCubeShaderUniforms()
600
+ const cfg = this.worldRenderer.worldRendererConfig
601
+ setCubeShadingTheme(this.cubeShaderMaterial, cfg.shadingTheme, cfg.cardinalLight)
534
602
  }
535
603
  return this.cubeShaderMaterial
536
604
  }
@@ -602,13 +670,16 @@ export class ChunkMeshManager {
602
670
  const wy = section.worldY
603
671
  const wz = section.worldZ
604
672
  if (wx !== undefined && wy !== undefined && wz !== undefined) {
605
- this.getGlobalLegacyBuffer().addSection(
673
+ const added = this.getGlobalLegacyBuffer().addSection(
606
674
  sectionKey,
607
675
  { positions, colors, skyLights, blockLights, uvs, indices },
608
676
  wx,
609
677
  wy,
610
678
  wz,
611
679
  )
680
+ if (added) {
681
+ this.registerLegacyCullSection(sectionKey, wx, wy, wz)
682
+ }
612
683
  }
613
684
  delete section.deferredLegacyOpaque
614
685
  }
@@ -619,13 +690,16 @@ export class ChunkMeshManager {
619
690
  const wy = section.worldY
620
691
  const wz = section.worldZ
621
692
  if (wx !== undefined && wy !== undefined && wz !== undefined) {
622
- this.getGlobalLegacyBlendBuffer().addSection(
693
+ const added = this.getGlobalLegacyBlendBuffer().addSection(
623
694
  sectionKey,
624
695
  { positions, colors, skyLights, blockLights, uvs, indices },
625
696
  wx,
626
697
  wy,
627
698
  wz,
628
699
  )
700
+ if (added) {
701
+ this.registerLegacyCullSection(sectionKey, wx, wy, wz)
702
+ }
629
703
  }
630
704
  delete section.deferredLegacyBlend
631
705
  section.hasBlendMesh = false
@@ -653,15 +727,13 @@ export class ChunkMeshManager {
653
727
  const far = raycaster.far
654
728
  const halfExtent = LEGACY_SECTION_HALF_EXTENT + 0.01
655
729
  const candidates: string[] = []
656
- for (const [key, section] of Object.entries(this.sectionObjects)) {
657
- if (section.worldX === undefined) continue
730
+ for (const [key] of this.legacyCullSections) {
731
+ const section = this.sectionObjects[key]
732
+ if (!section || section.worldX === undefined) continue
658
733
  const dx = section.worldX - origin.x
659
734
  const dy = (section.worldY ?? 0) - origin.y
660
735
  const dz = (section.worldZ ?? 0) - origin.z
661
736
  if (dx * dx + dy * dy + dz * dz > maxDistSq) continue
662
- const inOpaque = this.globalLegacyBuffer?.hasSection(key) ?? false
663
- const inBlend = this.globalLegacyBlendBuffer?.hasSection(key) ?? false
664
- if (!inOpaque && !inBlend) continue
665
737
  if (!sectionAabbIntersectsRay(
666
738
  section.worldX,
667
739
  section.worldY ?? 0,
@@ -905,7 +977,9 @@ export class ChunkMeshManager {
905
977
  geometryData.sy,
906
978
  geometryData.sz,
907
979
  )
908
- if (!added) {
980
+ if (added) {
981
+ this.registerLegacyCullSection(sectionKey, geometryData.sx, geometryData.sy, geometryData.sz)
982
+ } else {
909
983
  const poolEntry = this.acquirePooledSectionMesh(sectionKey)
910
984
  if (!poolEntry) return null
911
985
  legacyMesh = this.uploadLegacyPooledMesh(poolEntry, geometryData, geometryData.sx, geometryData.sy, geometryData.sz)
@@ -953,7 +1027,9 @@ export class ChunkMeshManager {
953
1027
  geometryData.sy,
954
1028
  geometryData.sz,
955
1029
  )
956
- if (!added) {
1030
+ if (added) {
1031
+ this.registerLegacyCullSection(sectionKey, geometryData.sx, geometryData.sy, geometryData.sz)
1032
+ } else {
957
1033
  console.warn(`ChunkMeshManager: blend invariant violation for section ${sectionKey}, using pooled mesh fallback`)
958
1034
  const poolEntry = this.acquirePooledSectionMesh(sectionKey)
959
1035
  if (!poolEntry) return null
@@ -1190,6 +1266,7 @@ export class ChunkMeshManager {
1190
1266
  sectionObject.visible = true
1191
1267
  }
1192
1268
  delete this.waitingChunksToDisplay[chunkKey]
1269
+ this.markCullDirty()
1193
1270
  }
1194
1271
 
1195
1272
  // Re-check every parked entry; each has its own grace window via `ageMs`.
@@ -1352,6 +1429,7 @@ export class ChunkMeshManager {
1352
1429
  this.globalBlockBuffer?.removeSection(sectionKey)
1353
1430
  this.globalLegacyBuffer?.removeSection(sectionKey)
1354
1431
  this.globalLegacyBlendBuffer?.removeSection(sectionKey)
1432
+ this.maybeUnregisterLegacyCullSection(sectionKey)
1355
1433
  this.unregisterShaderSectionRaycastBox(sectionKey)
1356
1434
  this.markCullDirty()
1357
1435
  delete sectionObject.deferredLegacyOpaque
@@ -69,6 +69,7 @@ export class GlobalBlockBuffer {
69
69
  private tierCAttrs: TierCAttrState | null = null
70
70
  private tierCGl: WebGL2RenderingContext | null = null
71
71
  private debugOverlay = false
72
+ private layoutVersion = 0
72
73
 
73
74
  constructor (
74
75
  material: THREE.ShaderMaterial,
@@ -225,6 +226,11 @@ export class GlobalBlockBuffer {
225
226
  this.sectionSlots.set(sectionKey, slot)
226
227
  this.markDirty(slot.start, slot.start + faceCount - 1)
227
228
  this.mesh.geometry.instanceCount = this.highWatermark
229
+ this.layoutVersion++
230
+ }
231
+
232
+ getLayoutVersion (): number {
233
+ return this.layoutVersion
228
234
  }
229
235
 
230
236
  hasSection (sectionKey: string): boolean {
@@ -288,6 +294,7 @@ export class GlobalBlockBuffer {
288
294
  this.insertFreeSlot(slot)
289
295
  this.shrinkHighWatermark()
290
296
  this.mesh.geometry.instanceCount = this.highWatermark
297
+ this.layoutVersion++
291
298
  }
292
299
 
293
300
  /** One interior-hole move per frame when fragmentation exceeds threshold; deferred shrink. */
@@ -318,6 +325,7 @@ export class GlobalBlockBuffer {
318
325
  this.sectionSlots.set(section.key, { start: newStart, count: section.count })
319
326
  this.markDirty(newStart, newStart + section.count - 1)
320
327
  this.pendingMove = { key: section.key, oldStart, newStart, count: section.count }
328
+ this.layoutVersion++
321
329
  }
322
330
 
323
331
  uploadDirtyRange (): void {