minecraft-renderer 0.1.41 → 0.1.43

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.
@@ -519,6 +519,167 @@ const meshColumnFromParsedV16V17 = (
519
519
  }
520
520
  }
521
521
 
522
+ // ---------------------------------------------------------------------------
523
+ // Fused multi-column helpers.
524
+ // Zero-alloc: reuse existing TypedArray buffers from caches, no concat.
525
+ // ---------------------------------------------------------------------------
526
+
527
+ const meshMultiColumnsFromRawV18Plus = (
528
+ chunksToUse: Array<{ x: number, z: number, chunk: any }>,
529
+ x: number,
530
+ z: number,
531
+ worldMinY: number,
532
+ worldMaxY: number,
533
+ meta: ReturnType<typeof getBlockMeta>
534
+ ): any | null => {
535
+ if (!wasm || !(wasm as any).generateGeometryFromMapChunkV18PlusMulti) return null
536
+ const chunkCount = chunksToUse.length
537
+ if (chunkCount === 0) return null
538
+
539
+ const rawPackets: Uint8Array[] = []
540
+ const numSectionsList = new Uint32Array(chunkCount)
541
+ const chunkXs = new Int32Array(chunkCount)
542
+ const chunkZs = new Int32Array(chunkCount)
543
+ let protocol = 0
544
+
545
+ for (let i = 0; i < chunkCount; i++) {
546
+ const raw = rawMapChunkCache.get(rawCacheKey(chunksToUse[i].x, chunksToUse[i].z))
547
+ if (!raw || raw.protocol < 757) return null
548
+ rawPackets.push(raw.rawPacket)
549
+ numSectionsList[i] = raw.numSections
550
+ chunkXs[i] = chunksToUse[i].x
551
+ chunkZs[i] = chunksToUse[i].z
552
+ if (i === 0) protocol = raw.protocol
553
+ }
554
+
555
+ const columnHeight = worldMaxY - worldMinY
556
+ try {
557
+ return (wasm as any).generateGeometryFromMapChunkV18PlusMulti(
558
+ rawPackets,
559
+ numSectionsList,
560
+ MAX_BITS_PER_BLOCK,
561
+ MAX_BITS_PER_BIOME,
562
+ protocol,
563
+ chunkXs,
564
+ chunkZs,
565
+ x, worldMinY, z, columnHeight,
566
+ worldMinY, worldMaxY,
567
+ worldMinY,
568
+ meta.invisibleBlocks,
569
+ meta.transparentBlocks,
570
+ meta.noAoBlocks,
571
+ meta.cullIdenticalBlocks,
572
+ meta.occludingBlocks,
573
+ config?.enableLighting !== false,
574
+ config?.smoothLighting !== false,
575
+ config?.skyLight || 15
576
+ )
577
+ } catch (err) {
578
+ console.warn('[WASM Mesher] generateGeometryFromMapChunkV18PlusMulti failed:', err)
579
+ return null
580
+ }
581
+ }
582
+
583
+ const meshMultiColumnsFromParsedV16V17 = (
584
+ chunksToUse: Array<{ x: number, z: number, chunk: any }>,
585
+ x: number,
586
+ z: number,
587
+ worldMinY: number,
588
+ worldMaxY: number,
589
+ meta: ReturnType<typeof getBlockMeta>
590
+ ): any | null => {
591
+ if (!wasm || !(wasm as any).generateGeometryFromParsedV16V17Multi) return null
592
+ const chunkCount = chunksToUse.length
593
+ if (chunkCount === 0) return null
594
+
595
+ // Determine which cache family owns all columns. Homogeneity is
596
+ // guaranteed by the server protocol version: v16 and v17 caches are
597
+ // never populated simultaneously within a session.
598
+ let family: 'v17' | 'v16' | null = null
599
+ for (let i = 0; i < chunkCount; i++) {
600
+ const key = rawCacheKey(chunksToUse[i].x, chunksToUse[i].z)
601
+ if (parsedV17Cache.has(key)) {
602
+ if (family === 'v16') return null
603
+ family = 'v17'
604
+ } else if (parsedV16Cache.has(key)) {
605
+ if (family === 'v17') return null
606
+ family = 'v16'
607
+ } else {
608
+ return null
609
+ }
610
+ }
611
+ if (!family) return null
612
+
613
+ const chunkDataList: Uint8Array[] = []
614
+ const biomesList: Int32Array[] = []
615
+ const skyLightList: Uint8Array[] = []
616
+ const blockLightList: Uint8Array[] = []
617
+ const numSectionsList = new Uint32Array(chunkCount)
618
+ const chunkXs = new Int32Array(chunkCount)
619
+ const chunkZs = new Int32Array(chunkCount)
620
+ const bitMapLoHi = new Uint32Array(chunkCount * 2)
621
+ let maxBitsPerBlock = 15
622
+
623
+ for (let i = 0; i < chunkCount; i++) {
624
+ const key = rawCacheKey(chunksToUse[i].x, chunksToUse[i].z)
625
+ if (family === 'v17') {
626
+ const entry = parsedV17Cache.get(key)!
627
+ chunkDataList.push(entry.chunkData)
628
+ biomesList.push(entry.biomes ?? new Int32Array(0))
629
+ numSectionsList[i] = entry.numSections
630
+ bitMapLoHi[i * 2] = entry.bitMapLoHi[0]
631
+ bitMapLoHi[i * 2 + 1] = entry.bitMapLoHi[1]
632
+ if (i === 0) maxBitsPerBlock = entry.maxBitsPerBlock
633
+ const light = updateLightV17Cache.get(key)
634
+ skyLightList.push(light?.skyLight ?? new Uint8Array(0))
635
+ blockLightList.push(light?.blockLight ?? new Uint8Array(0))
636
+ } else {
637
+ const entry = parsedV16Cache.get(key)!
638
+ chunkDataList.push(entry.chunkData)
639
+ biomesList.push(entry.biomes ?? new Int32Array(0))
640
+ numSectionsList[i] = 16
641
+ const bm = entry.bitMap >>> 0
642
+ bitMapLoHi[i * 2] = bm
643
+ bitMapLoHi[i * 2 + 1] = 0
644
+ const light = updateLightV16Cache.get(key)
645
+ skyLightList.push(light?.skyLight ?? new Uint8Array(0))
646
+ blockLightList.push(light?.blockLight ?? new Uint8Array(0))
647
+ }
648
+ chunkXs[i] = chunksToUse[i].x
649
+ chunkZs[i] = chunksToUse[i].z
650
+ }
651
+
652
+ const columnHeight = worldMaxY - worldMinY
653
+ try {
654
+ return (wasm as any).generateGeometryFromParsedV16V17Multi(
655
+ chunkDataList,
656
+ bitMapLoHi,
657
+ numSectionsList,
658
+ maxBitsPerBlock,
659
+ biomesList,
660
+ 1,
661
+ skyLightList,
662
+ blockLightList,
663
+ chunkXs,
664
+ chunkZs,
665
+ x, worldMinY, z, columnHeight,
666
+ worldMinY, worldMaxY,
667
+ worldMinY,
668
+ meta.invisibleBlocks,
669
+ meta.transparentBlocks,
670
+ meta.noAoBlocks,
671
+ meta.cullIdenticalBlocks,
672
+ meta.occludingBlocks,
673
+ config?.enableLighting !== false,
674
+ config?.smoothLighting !== false,
675
+ config?.skyLight || 15
676
+ )
677
+ } catch (err) {
678
+ console.warn('[WASM Mesher] generateGeometryFromParsedV16V17Multi failed:', err)
679
+ return null
680
+ }
681
+ }
682
+
522
683
  const handleMessage = async (data: any) => {
523
684
  const globalVar: any = globalThis
524
685
 
@@ -885,6 +1046,8 @@ function processColumnTick() {
885
1046
  let t1 = 0
886
1047
  let usedFusedPath = false
887
1048
 
1049
+ const meta = getBlockMeta(version)
1050
+
888
1051
  // ------------------------------------------------------------------
889
1052
  // Fused fast-path: for single-column meshing (no neighbours), parse
890
1053
  // and mesh in ONE WASM call so no typed arrays leave Rust memory.
@@ -894,7 +1057,6 @@ function processColumnTick() {
894
1057
  const rawEntry = rawMapChunkCache.get(rawCacheKey(x, z))
895
1058
  const v17Entry = parsedV17Cache.get(rawCacheKey(x, z))
896
1059
  const v16Entry = parsedV16Cache.get(rawCacheKey(x, z))
897
- const meta = getBlockMeta(version)
898
1060
 
899
1061
  if (rawEntry) {
900
1062
  wasmResult = meshColumnFromRawV18Plus(rawEntry, x, z, worldMinY, worldMaxY, meta)
@@ -922,7 +1084,23 @@ function processColumnTick() {
922
1084
  t1 = performance.now()
923
1085
  wasmPhase = t1 - t0
924
1086
  preTargetConvert = wasmPhase
925
- // prePhase stays 0 — no JS conversion loop ran.
1087
+ }
1088
+ }
1089
+
1090
+ // ------------------------------------------------------------------
1091
+ // Fused multi-column fast-path: parse+mesh all columns in one WASM
1092
+ // call with zero JS typed-array allocation.
1093
+ // Falls back to the old two-step path when the cache is incomplete
1094
+ // or any helper returns null.
1095
+ // ------------------------------------------------------------------
1096
+ if (!wasmResult && chunkCount > 1) {
1097
+ wasmResult = meshMultiColumnsFromRawV18Plus(chunksToUse, x, z, worldMinY, worldMaxY, meta)
1098
+ ?? meshMultiColumnsFromParsedV16V17(chunksToUse, x, z, worldMinY, worldMaxY, meta)
1099
+ if (wasmResult) {
1100
+ usedFusedPath = true
1101
+ t1 = performance.now()
1102
+ wasmPhase = t1 - t0
1103
+ preTargetConvert = wasmPhase
926
1104
  }
927
1105
  }
928
1106
 
@@ -1169,6 +1347,20 @@ function processColumnTick() {
1169
1347
  //@ts-ignore
1170
1348
  geometry.indices?.buffer,
1171
1349
  ].filter(Boolean)
1350
+
1351
+ if (exported.geometry.indices.length > 0 && config.computeWireframeEdges) {
1352
+ try {
1353
+ const wireframeF32 = geometry.indices instanceof Uint32Array
1354
+ ? wasm!.computeWireframeEdges(geometry.positions as Float32Array, geometry.indices)
1355
+ : wasm!.computeWireframeEdgesU16(geometry.positions as Float32Array, geometry.indices as Uint16Array)
1356
+ if (wireframeF32.length > 0) {
1357
+ geometry.wireframePositions = wireframeF32
1358
+ transferable.push(wireframeF32.buffer)
1359
+ }
1360
+ } catch (err) {
1361
+ // Fall through — sciFiWorldReveal will fall back to main-thread computation
1362
+ }
1363
+ }
1172
1364
  } else {
1173
1365
  geometry = makeEmptyColumnGeometry(sx, sy, sz, sectionHeight, false)
1174
1366
  // Still attach block entity metadata so the main thread sees