minecraft-renderer 0.1.87 → 0.1.89

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.
@@ -22,8 +22,30 @@ function makeQuadGeometry(): {
22
22
  }
23
23
  }
24
24
 
25
+ /** Two quads along +Z: quad 0 centered at z=0, quad 1 centered at z=8. */
26
+ function makeTwoQuadGeometry(): {
27
+ positions: Float32Array
28
+ colors: Float32Array
29
+ skyLights: Float32Array
30
+ blockLights: Float32Array
31
+ uvs: Float32Array
32
+ indices: Uint32Array
33
+ } {
34
+ return {
35
+ positions: new Float32Array([-1, 0, -1, 1, 0, -1, 1, 0, 1, -1, 0, 1, -1, 0, 7, 1, 0, 7, 1, 0, 9, -1, 0, 9]),
36
+ colors: new Float32Array(24).fill(1),
37
+ skyLights: new Float32Array(8).fill(1),
38
+ blockLights: new Float32Array(8).fill(0),
39
+ uvs: new Float32Array(16).fill(0),
40
+ indices: new Uint32Array([0, 1, 2, 0, 2, 3, 4, 6, 5, 4, 5, 6])
41
+ }
42
+ }
43
+
25
44
  type BufferInternals = {
26
45
  pendingRanges: Array<{ start: number; end: number }>
46
+ indexPendingRanges: Array<{ start: number; end: number }>
47
+ quadCentroids: Float32Array
48
+ quadIndexTemplate: Uint8Array
27
49
  pendingMove: { key: string; oldStart: number; newStart: number; count: number } | null
28
50
  growCapacity: (minQuads: number) => void
29
51
  }
@@ -36,6 +58,10 @@ function drainUploads(buffer: GlobalLegacyBuffer): void {
36
58
  while (getInternals(buffer).pendingRanges.length) buffer.uploadDirtyRange()
37
59
  }
38
60
 
61
+ function drainIndexUploads(buffer: GlobalLegacyBuffer): void {
62
+ while (buffer.hasPendingIndexUploads()) buffer.uploadDirtyIndexRange()
63
+ }
64
+
39
65
  function finishCurrentMove(buffer: GlobalLegacyBuffer): void {
40
66
  drainUploads(buffer)
41
67
  buffer.compactStep()
@@ -675,6 +701,21 @@ test('GlobalLegacyBuffer: carveSpansAroundPendingRanges splits merged span', ()
675
701
  ])
676
702
  })
677
703
 
704
+ test('GlobalLegacyBuffer: carved merged adjacent sections stay within union of input ranges', () => {
705
+ const sectionRanges = [
706
+ { start: 100, count: 10 },
707
+ { start: 110, count: 10 }
708
+ ]
709
+ const merged = [{ start: 100, count: 20 }]
710
+ const carved = carveSpansAroundPendingRanges(merged, [{ start: 105, end: 114 }])
711
+ for (const span of carved) {
712
+ for (let q = span.start; q < span.start + span.count; q++) {
713
+ const inUnion = sectionRanges.some(r => q >= r.start && q < r.start + r.count)
714
+ expect(inUnion, `quad ${q} not in union of section ranges`).toBe(true)
715
+ }
716
+ }
717
+ })
718
+
678
719
  test('GlobalLegacyBuffer: uploadEpoch increments when dirty range drains', () => {
679
720
  const scene = new THREE.Scene()
680
721
  const mat = createGlobalLegacyBlockMaterial()
@@ -760,3 +801,94 @@ test('GlobalLegacyBuffer: suppressThreeDraw uses minimal non-zero draw range', (
760
801
  buffer.dispose()
761
802
  mat.dispose()
762
803
  })
804
+
805
+ test('GlobalLegacyBuffer: reorderSectionBlendIndices sorts quads back-to-front', () => {
806
+ const scene = new THREE.Scene()
807
+ const mat = createGlobalLegacyBlockMaterial()
808
+ const buffer = new GlobalLegacyBuffer(mat, scene)
809
+ const geo = makeTwoQuadGeometry()
810
+
811
+ buffer.addSection('blend', geo, 0, 0, 0)
812
+ drainUploads(buffer)
813
+
814
+ const layoutBefore = buffer.getLayoutVersion()
815
+ const slot = buffer.getSectionSlot('blend')!
816
+ const vertBase0 = slot.start * 4
817
+ const vertBase1 = (slot.start + 1) * 4
818
+
819
+ expect(buffer.reorderSectionBlendIndices('blend', 0, 0, -20)).toBe(true)
820
+ expect(buffer.getLayoutVersion()).toBe(layoutBefore)
821
+
822
+ let indices = readSectionIndices(buffer, 'blend')
823
+ expect(indices.slice(0, 6)).toEqual([vertBase1, vertBase1 + 2, vertBase1 + 1, vertBase1, vertBase1 + 1, vertBase1 + 2])
824
+ expect(indices.slice(6, 12)).toEqual([vertBase0, vertBase0 + 1, vertBase0 + 2, vertBase0, vertBase0 + 2, vertBase0 + 3])
825
+
826
+ expect(buffer.reorderSectionBlendIndices('blend', 0, 0, 20)).toBe(true)
827
+ indices = readSectionIndices(buffer, 'blend')
828
+ expect(indices.slice(0, 6)).toEqual([vertBase0, vertBase0 + 1, vertBase0 + 2, vertBase0, vertBase0 + 2, vertBase0 + 3])
829
+ expect(indices.slice(6, 12)).toEqual([vertBase1, vertBase1 + 2, vertBase1 + 1, vertBase1, vertBase1 + 1, vertBase1 + 2])
830
+
831
+ expect(getInternals(buffer).indexPendingRanges.length).toBeGreaterThan(0)
832
+ drainIndexUploads(buffer)
833
+ expect(buffer.hasPendingIndexUploads()).toBe(false)
834
+
835
+ buffer.dispose()
836
+ mat.dispose()
837
+ })
838
+
839
+ test('GlobalLegacyBuffer: sortBlend pre-sorts indices on addSection before upload', () => {
840
+ const scene = new THREE.Scene()
841
+ const mat = createGlobalLegacyBlockMaterial()
842
+ const buffer = new GlobalLegacyBuffer(mat, scene, { sortBlend: true })
843
+ const geo = makeTwoQuadGeometry()
844
+
845
+ buffer.setCameraOrigin(0, 0, -20)
846
+ buffer.addSection('blend', geo, 0, 0, 0)
847
+
848
+ const slot = buffer.getSectionSlot('blend')!
849
+ const vertBase0 = slot.start * 4
850
+ const vertBase1 = (slot.start + 1) * 4
851
+ const indices = readSectionIndices(buffer, 'blend')
852
+
853
+ expect(indices.slice(0, 6)).toEqual([vertBase1, vertBase1 + 2, vertBase1 + 1, vertBase1, vertBase1 + 1, vertBase1 + 2])
854
+ expect(indices.slice(6, 12)).toEqual([vertBase0, vertBase0 + 1, vertBase0 + 2, vertBase0, vertBase0 + 2, vertBase0 + 3])
855
+ expect(getInternals(buffer).indexPendingRanges.length).toBe(0)
856
+
857
+ buffer.dispose()
858
+ mat.dispose()
859
+ })
860
+
861
+ test('GlobalLegacyBuffer: blend reorder metadata survives compaction', () => {
862
+ const scene = new THREE.Scene()
863
+ const mat = createGlobalLegacyBlockMaterial()
864
+ const buffer = new GlobalLegacyBuffer(mat, scene)
865
+ const geo = makeQuadGeometry()
866
+ const twoGeo = makeTwoQuadGeometry()
867
+
868
+ buffer.addSection('a', geo, 0, 0, 0)
869
+ buffer.addSection('b', geo, 8, 0, 0)
870
+ buffer.addSection('blend', twoGeo, 16, 0, 0)
871
+ expect(buffer.getSectionSlot('blend')).toEqual({ start: 2, count: 2 })
872
+
873
+ buffer.removeSection('a')
874
+ buffer.removeSection('b')
875
+ drainUploads(buffer)
876
+ buffer.compactStep()
877
+ finishCurrentMove(buffer)
878
+
879
+ const slot = buffer.getSectionSlot('blend')!
880
+ expect(slot).toEqual({ start: 0, count: 2 })
881
+
882
+ const internals = getInternals(buffer)
883
+ expect(internals.quadCentroids[slot.start * 3 + 2]).toBeCloseTo(0, 5)
884
+ expect(internals.quadCentroids[(slot.start + 1) * 3 + 2]).toBeCloseTo(8, 5)
885
+ expect(Array.from(internals.quadIndexTemplate.slice((slot.start + 1) * 6, (slot.start + 1) * 6 + 6))).toEqual([0, 2, 1, 0, 1, 2])
886
+
887
+ expect(buffer.reorderSectionBlendIndices('blend', 0, 0, -20)).toBe(true)
888
+ const vertBase1 = (slot.start + 1) * 4
889
+ const indices = readSectionIndices(buffer, 'blend')
890
+ expect(indices[0]).toBe(vertBase1)
891
+
892
+ buffer.dispose()
893
+ mat.dispose()
894
+ })
@@ -1327,11 +1327,13 @@ export class WorldRendererThree extends WorldRendererCommon {
1327
1327
  globalLegacyBuffer.suppressThreeDraw()
1328
1328
  }
1329
1329
  const globalLegacyBlendBuffer = this.chunkMeshManager.globalLegacyBlendBuffer
1330
+ let didBlendAllAttrUpload = false
1330
1331
  if (globalLegacyBlendBuffer) {
1331
1332
  globalLegacyBlendBuffer.setDebugOverlay(this.displayOptions.inWorldRenderingConfig.enableDebugOverlay)
1332
1333
  globalLegacyBlendBuffer.compactStep()
1333
1334
  if (globalLegacyBlendBuffer.hasPendingUploads()) {
1334
1335
  globalLegacyBlendBuffer.uploadDirtyRange()
1336
+ didBlendAllAttrUpload = true
1335
1337
  }
1336
1338
  globalLegacyBlendBuffer.suppressThreeDraw()
1337
1339
  }
@@ -1342,6 +1344,10 @@ export class WorldRendererThree extends WorldRendererCommon {
1342
1344
  this.chunkMeshManager.updateSectionCullAndSort(cam, this.cameraWorldPos.x, this.cameraWorldPos.y, this.cameraWorldPos.z)
1343
1345
  this.chunkMeshManager.clearCullDirty()
1344
1346
  }
1347
+ this.chunkMeshManager.sortVisibleBlendSections(camX, camY, camZ)
1348
+ if (!didBlendAllAttrUpload && globalLegacyBlendBuffer?.hasPendingIndexUploads()) {
1349
+ globalLegacyBlendBuffer.uploadDirtyIndexRange()
1350
+ }
1345
1351
  this.renderer.render(this.scene, cam)
1346
1352
 
1347
1353
  if (
@@ -279,6 +279,32 @@ test('culling regression: glass/leaves cluster — see-through blocks not over-c
279
279
  assertMesherParity(world, 20)
280
280
  })
281
281
 
282
+ test('culling regression: cactus on grass — ground top face not culled (issue #73)', () => {
283
+ const world = buildWorld([
284
+ { x: 0, y: 0, z: 0, name: 'grass_block' },
285
+ { x: 0, y: 1, z: 0, name: 'cactus' }
286
+ ])
287
+ // Pre-fix: grass top culled by cactus cosmetic cap → 14 quads. Post-fix: grass top drawn → 15.
288
+ assertMesherParity(world, 15)
289
+ })
290
+
291
+ test('culling regression: cactus stacked on cactus — no over-cull holes', () => {
292
+ const world = buildWorld([
293
+ { x: 0, y: 0, z: 0, name: 'cactus' },
294
+ { x: 0, y: 1, z: 0, name: 'cactus' }
295
+ ])
296
+ assertMesherParity(world, 12)
297
+ })
298
+
299
+ test('culling regression: cactus beside solid block — side faces still drawn', () => {
300
+ const world = buildWorld([
301
+ { x: 0, y: 0, z: 0, name: 'dirt' },
302
+ { x: 1, y: 0, z: 0, name: 'cactus' }
303
+ ])
304
+ expect(countQuadsFromLegacy(world)).toBeGreaterThan(9)
305
+ expect(countQuadsFromWasm(world, false)).toBeGreaterThan(9)
306
+ })
307
+
282
308
  test('shader cubes: dirt UP face culled under farmland', () => {
283
309
  const world = buildWorld([
284
310
  { x: 0, y: 0, z: 0, name: 'dirt' },