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.
- package/dist/mesher.js +20 -20
- package/dist/mesher.js.map +3 -3
- package/dist/mesherWasm.js +165 -165
- package/dist/minecraft-renderer.js +28 -28
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +355 -355
- package/package.json +1 -1
- package/src/mesher-shared/faceOcclusion.ts +56 -12
- package/src/mesher-shared/tests/faceOcclusion.test.ts +36 -0
- package/src/playground/scenes/partialBlockCulling.ts +14 -1
- package/src/three/chunkMeshManager.ts +35 -1
- package/src/three/cubeDrawSpans.ts +1 -9
- package/src/three/globalBlockBuffer.ts +0 -2
- package/src/three/globalLegacyBuffer.ts +174 -31
- package/src/three/shaders/legacyBlockShader.ts +1 -1
- package/src/three/tests/cubeDrawSpans.test.ts +31 -0
- package/src/three/tests/globalLegacyBuffer.test.ts +132 -0
- package/src/three/worldRendererThree.ts +6 -0
- package/src/wasm-mesher/tests/cullingRegression.test.ts +26 -0
|
@@ -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' },
|