minecraft-renderer 0.1.87 → 0.1.88
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/minecraft-renderer.js +61 -61
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +108 -108
- package/package.json +1 -1
- package/src/three/chunkMeshManager.ts +35 -1
- package/src/three/globalLegacyBuffer.ts +174 -0
- package/src/three/shaders/legacyBlockShader.ts +1 -1
- package/src/three/tests/globalLegacyBuffer.test.ts +117 -0
- package/src/three/worldRendererThree.ts +6 -0
package/package.json
CHANGED
|
@@ -159,6 +159,11 @@ export class ChunkMeshManager {
|
|
|
159
159
|
private readonly _lastCullCamQuat = new THREE.Quaternion()
|
|
160
160
|
private readonly _cullViewQuat = new THREE.Quaternion()
|
|
161
161
|
private _cullCamInitialized = false
|
|
162
|
+
/** Visible blend section keys from last cull pass — used by per-quad blend sort. */
|
|
163
|
+
private _visibleBlendKeys: string[] = []
|
|
164
|
+
private readonly _lastBlendSortPos = new THREE.Vector3()
|
|
165
|
+
private _lastBlendSortLayoutVersion = -1
|
|
166
|
+
private _blendSortPosInitialized = false
|
|
162
167
|
/** One instanced mesh for all shader-cube faces (single draw call). */
|
|
163
168
|
globalBlockBuffer: GlobalBlockBuffer | null = null
|
|
164
169
|
globalLegacyBuffer: GlobalLegacyBuffer | null = null
|
|
@@ -328,7 +333,8 @@ export class ChunkMeshManager {
|
|
|
328
333
|
this.globalLegacyBlendBuffer = new GlobalLegacyBuffer(this.getGlobalLegacyBlendShaderMaterial(), this.scene, {
|
|
329
334
|
name: 'globalLegacyBlend',
|
|
330
335
|
initialCapacityQuads: 32_000,
|
|
331
|
-
growthIncrementQuads: 32_000
|
|
336
|
+
growthIncrementQuads: 32_000,
|
|
337
|
+
sortBlend: true
|
|
332
338
|
})
|
|
333
339
|
this.globalLegacyBlendBuffer.setRenderOrigin(this.renderOrigin)
|
|
334
340
|
}
|
|
@@ -459,6 +465,7 @@ export class ChunkMeshManager {
|
|
|
459
465
|
const blendVisible = visible.filter(v => blendBuf?.hasSection(v.key))
|
|
460
466
|
blendVisible.sort((a, b) => b.distSq - a.distSq)
|
|
461
467
|
const blendKeys = blendVisible.map(v => v.key)
|
|
468
|
+
this._visibleBlendKeys = blendKeys
|
|
462
469
|
|
|
463
470
|
const cubeVisibleKeys: string[] = []
|
|
464
471
|
const visibleSlots: Array<{ start: number; count: number }> = []
|
|
@@ -532,6 +539,33 @@ export class ChunkMeshManager {
|
|
|
532
539
|
this.updatePooledLegacyCullState(cameraWorldX, cameraWorldY, cameraWorldZ)
|
|
533
540
|
}
|
|
534
541
|
|
|
542
|
+
private static readonly BLEND_RESORT_DISTANCE = 1.0
|
|
543
|
+
|
|
544
|
+
/** Per-quad back-to-front reorder within visible blend sections; throttled by camera translation. */
|
|
545
|
+
sortVisibleBlendSections(camX: number, camY: number, camZ: number): void {
|
|
546
|
+
const blendBuf = this.globalLegacyBlendBuffer
|
|
547
|
+
if (!blendBuf || this._visibleBlendKeys.length === 0) return
|
|
548
|
+
|
|
549
|
+
const layoutVersion = blendBuf.getLayoutVersion()
|
|
550
|
+
const layoutChanged = layoutVersion !== this._lastBlendSortLayoutVersion
|
|
551
|
+
if (this._blendSortPosInitialized && !layoutChanged) {
|
|
552
|
+
const dx = camX - this._lastBlendSortPos.x
|
|
553
|
+
const dy = camY - this._lastBlendSortPos.y
|
|
554
|
+
const dz = camZ - this._lastBlendSortPos.z
|
|
555
|
+
if (dx * dx + dy * dy + dz * dz < ChunkMeshManager.BLEND_RESORT_DISTANCE * ChunkMeshManager.BLEND_RESORT_DISTANCE) {
|
|
556
|
+
return
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
for (const key of this._visibleBlendKeys) {
|
|
561
|
+
blendBuf.reorderSectionBlendIndices(key, camX, camY, camZ)
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
this._lastBlendSortPos.set(camX, camY, camZ)
|
|
565
|
+
this._blendSortPosInitialized = true
|
|
566
|
+
this._lastBlendSortLayoutVersion = layoutVersion
|
|
567
|
+
}
|
|
568
|
+
|
|
535
569
|
private bufferStateKey(): string {
|
|
536
570
|
const b = this.globalBlockBuffer
|
|
537
571
|
const o = this.globalLegacyBuffer
|
|
@@ -96,6 +96,8 @@ export type GlobalLegacyBufferOptions = {
|
|
|
96
96
|
name?: string
|
|
97
97
|
initialCapacityQuads?: number
|
|
98
98
|
growthIncrementQuads?: number
|
|
99
|
+
/** When true, pre-sort blend indices on addSection and support per-quad reorder. */
|
|
100
|
+
sortBlend?: boolean
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
export type VisibleSectionSpan = { key: string; distSq: number }
|
|
@@ -126,6 +128,7 @@ export class GlobalLegacyBuffer {
|
|
|
126
128
|
readonly material: THREE.ShaderMaterial
|
|
127
129
|
|
|
128
130
|
private readonly growthIncrementQuads: number
|
|
131
|
+
private readonly blendSort: boolean
|
|
129
132
|
private capacityQuads: number
|
|
130
133
|
private positions: Float32Array
|
|
131
134
|
private colors: Float32Array
|
|
@@ -134,10 +137,15 @@ export class GlobalLegacyBuffer {
|
|
|
134
137
|
private uvs: Float32Array
|
|
135
138
|
private aOrigin: Float32Array
|
|
136
139
|
private indices: Uint32Array
|
|
140
|
+
/** Section-relative world centroid per physical quad (3 floats each). */
|
|
141
|
+
private quadCentroids: Float32Array
|
|
142
|
+
/** Per-quad local index template (6 bytes each, values 0..3). */
|
|
143
|
+
private quadIndexTemplate: Uint8Array
|
|
137
144
|
private readonly sectionSlots = new Map<string, { start: number; count: number }>()
|
|
138
145
|
private freeList: Array<{ start: number; count: number }> = []
|
|
139
146
|
private highWatermark = 0
|
|
140
147
|
private pendingRanges: Array<{ start: number; end: number }> = []
|
|
148
|
+
private indexPendingRanges: Array<{ start: number; end: number }> = []
|
|
141
149
|
private readonly _spanScratch: Array<{ start: number; count: number }> = []
|
|
142
150
|
private renderOrigin: RenderOrigin = { x: 0, y: 0, z: 0 }
|
|
143
151
|
private layoutVersion = 0
|
|
@@ -146,12 +154,20 @@ export class GlobalLegacyBuffer {
|
|
|
146
154
|
private uploadEpoch = 0
|
|
147
155
|
private visibleIndexSpans: LegacyDrawSpan[] = []
|
|
148
156
|
private readonly _drawScratch: LegacyMultiDrawScratch = createLegacyMultiDrawScratch()
|
|
157
|
+
/** Reusable scratch for applyBlendSort; grown on demand, never allocated per call. */
|
|
158
|
+
private _sortOrder: Uint32Array = new Uint32Array(0)
|
|
159
|
+
private _sortDistSq: Float64Array = new Float64Array(0)
|
|
149
160
|
private multiDrawCaps: LegacyMultiDrawCaps | null = null
|
|
150
161
|
private debugOverlay = false
|
|
162
|
+
private _camX = 0
|
|
163
|
+
private _camY = 0
|
|
164
|
+
private _camZ = 0
|
|
165
|
+
private _camInitialized = false
|
|
151
166
|
|
|
152
167
|
constructor(material: THREE.ShaderMaterial, scene: THREE.Object3D, opts?: GlobalLegacyBufferOptions) {
|
|
153
168
|
this.material = material
|
|
154
169
|
this.growthIncrementQuads = opts?.growthIncrementQuads ?? DEFAULT_GROWTH_INCREMENT_QUADS
|
|
170
|
+
this.blendSort = opts?.sortBlend ?? false
|
|
155
171
|
this.capacityQuads = opts?.initialCapacityQuads ?? DEFAULT_INITIAL_CAPACITY_QUADS
|
|
156
172
|
const maxVerts = this.capacityQuads * VERTS_PER_QUAD
|
|
157
173
|
this.positions = new Float32Array(maxVerts * FLOATS_PER_VERT)
|
|
@@ -161,6 +177,8 @@ export class GlobalLegacyBuffer {
|
|
|
161
177
|
this.uvs = new Float32Array(maxVerts * FLOATS_PER_UV_VERT)
|
|
162
178
|
this.aOrigin = new Float32Array(maxVerts * FLOATS_PER_VERT)
|
|
163
179
|
this.indices = new Uint32Array(this.capacityQuads * INDICES_PER_QUAD)
|
|
180
|
+
this.quadCentroids = new Float32Array(this.capacityQuads * 3)
|
|
181
|
+
this.quadIndexTemplate = new Uint8Array(this.capacityQuads * INDICES_PER_QUAD)
|
|
164
182
|
|
|
165
183
|
const geometry = new THREE.BufferGeometry()
|
|
166
184
|
const mkAttr = (arr: Float32Array, itemSize: number, name: string) => {
|
|
@@ -305,10 +323,38 @@ export class GlobalLegacyBuffer {
|
|
|
305
323
|
this.indices[dstIndexBase + i] = geo.indices[i]! + vertexBase
|
|
306
324
|
}
|
|
307
325
|
|
|
326
|
+
for (let q = 0; q < quadCount; q++) {
|
|
327
|
+
const physQuad = slot.start + q
|
|
328
|
+
const localVertBase = q * VERTS_PER_QUAD
|
|
329
|
+
const posBase = localVertBase * FLOATS_PER_VERT
|
|
330
|
+
let cx = 0
|
|
331
|
+
let cy = 0
|
|
332
|
+
let cz = 0
|
|
333
|
+
for (let v = 0; v < VERTS_PER_QUAD; v++) {
|
|
334
|
+
const p = posBase + v * FLOATS_PER_VERT
|
|
335
|
+
cx += geo.positions[p]!
|
|
336
|
+
cy += geo.positions[p + 1]!
|
|
337
|
+
cz += geo.positions[p + 2]!
|
|
338
|
+
}
|
|
339
|
+
const centBase = physQuad * 3
|
|
340
|
+
this.quadCentroids[centBase] = cx / VERTS_PER_QUAD
|
|
341
|
+
this.quadCentroids[centBase + 1] = cy / VERTS_PER_QUAD
|
|
342
|
+
this.quadCentroids[centBase + 2] = cz / VERTS_PER_QUAD
|
|
343
|
+
|
|
344
|
+
const idxBase = q * INDICES_PER_QUAD
|
|
345
|
+
const tmplBase = physQuad * INDICES_PER_QUAD
|
|
346
|
+
for (let i = 0; i < INDICES_PER_QUAD; i++) {
|
|
347
|
+
this.quadIndexTemplate[tmplBase + i] = geo.indices[idxBase + i]! - localVertBase
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
308
351
|
this.sectionSlots.set(sectionKey, slot)
|
|
309
352
|
if (isRemesh && previousSlot) {
|
|
310
353
|
this.pendingReplace.set(sectionKey, { oldStart: previousSlot.start, oldCount: previousSlot.count })
|
|
311
354
|
}
|
|
355
|
+
if (this.blendSort && this._camInitialized && quadCount >= 2) {
|
|
356
|
+
this.applyBlendSort(slot.start, quadCount, this._camX, this._camY, this._camZ)
|
|
357
|
+
}
|
|
312
358
|
this.markDirty(slot.start, slot.start + quadCount - 1)
|
|
313
359
|
this.syncDefaultDrawGroups()
|
|
314
360
|
this.layoutVersion++
|
|
@@ -558,6 +604,70 @@ export class GlobalLegacyBuffer {
|
|
|
558
604
|
return this.pendingRanges.length > 0
|
|
559
605
|
}
|
|
560
606
|
|
|
607
|
+
hasPendingIndexUploads(): boolean {
|
|
608
|
+
return this.indexPendingRanges.length > 0
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Reorder a section's index buffer back-to-front by quad centroid distance to camera.
|
|
613
|
+
* Does not bump layoutVersion or uploadEpoch (draw spans unchanged).
|
|
614
|
+
*/
|
|
615
|
+
reorderSectionBlendIndices(sectionKey: string, camX: number, camY: number, camZ: number): boolean {
|
|
616
|
+
const slot = this.sectionSlots.get(sectionKey)
|
|
617
|
+
if (!slot) return false
|
|
618
|
+
if (this.pendingMove?.key === sectionKey) return false
|
|
619
|
+
if (this.pendingReplace.has(sectionKey)) return false
|
|
620
|
+
if (!this.rangeFullyUploaded(slot.start, slot.start + slot.count - 1)) return false
|
|
621
|
+
if (slot.count < 2) return false
|
|
622
|
+
|
|
623
|
+
this.applyBlendSort(slot.start, slot.count, camX, camY, camZ)
|
|
624
|
+
this.markIndexDirty(slot.start, slot.start + slot.count - 1)
|
|
625
|
+
return true
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/** Rewrite indices for [slotStart, slotStart+slotCount) back-to-front by quad centroid distance. */
|
|
629
|
+
private applyBlendSort(slotStart: number, slotCount: number, camX: number, camY: number, camZ: number): void {
|
|
630
|
+
const dstFloatBase = slotStart * VERTS_PER_QUAD * FLOATS_PER_VERT
|
|
631
|
+
const sx = this.aOrigin[dstFloatBase]! + this.renderOrigin.x
|
|
632
|
+
const sy = this.aOrigin[dstFloatBase + 1]! + this.renderOrigin.y
|
|
633
|
+
const sz = this.aOrigin[dstFloatBase + 2]! + this.renderOrigin.z
|
|
634
|
+
|
|
635
|
+
if (this._sortOrder.length < slotCount) {
|
|
636
|
+
this._sortOrder = new Uint32Array(slotCount)
|
|
637
|
+
this._sortDistSq = new Float64Array(slotCount)
|
|
638
|
+
}
|
|
639
|
+
const order = this._sortOrder
|
|
640
|
+
const distSq = this._sortDistSq
|
|
641
|
+
for (let p = 0; p < slotCount; p++) {
|
|
642
|
+
order[p] = p
|
|
643
|
+
const physQuad = slotStart + p
|
|
644
|
+
const centBase = physQuad * 3
|
|
645
|
+
const wx = sx + this.quadCentroids[centBase]!
|
|
646
|
+
const wy = sy + this.quadCentroids[centBase + 1]!
|
|
647
|
+
const wz = sz + this.quadCentroids[centBase + 2]!
|
|
648
|
+
const dx = wx - camX
|
|
649
|
+
const dy = wy - camY
|
|
650
|
+
const dz = wz - camZ
|
|
651
|
+
distSq[p] = dx * dx + dy * dy + dz * dz
|
|
652
|
+
}
|
|
653
|
+
const orderView = order.subarray(0, slotCount)
|
|
654
|
+
orderView.sort((a, b) => {
|
|
655
|
+
const d = distSq[b]! - distSq[a]!
|
|
656
|
+
if (d !== 0) return d
|
|
657
|
+
return a - b
|
|
658
|
+
})
|
|
659
|
+
|
|
660
|
+
for (let k = 0; k < slotCount; k++) {
|
|
661
|
+
const src = orderView[k]!
|
|
662
|
+
const srcVertBase = (slotStart + src) * VERTS_PER_QUAD
|
|
663
|
+
const dstIdxBase = (slotStart + k) * INDICES_PER_QUAD
|
|
664
|
+
const tmplBase = (slotStart + src) * INDICES_PER_QUAD
|
|
665
|
+
for (let i = 0; i < INDICES_PER_QUAD; i++) {
|
|
666
|
+
this.indices[dstIdxBase + i] = srcVertBase + this.quadIndexTemplate[tmplBase + i]!
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
561
671
|
uploadDirtyRange(): void {
|
|
562
672
|
const r = this.pendingRanges[0]
|
|
563
673
|
if (!r) return
|
|
@@ -613,6 +723,27 @@ export class GlobalLegacyBuffer {
|
|
|
613
723
|
this.uploadEpoch++
|
|
614
724
|
}
|
|
615
725
|
|
|
726
|
+
uploadDirtyIndexRange(): void {
|
|
727
|
+
const r = this.indexPendingRanges[0]
|
|
728
|
+
if (!r) return
|
|
729
|
+
|
|
730
|
+
const quadOffset = r.start
|
|
731
|
+
const quadCount = Math.min(r.end - r.start + 1, MAX_UPLOAD_QUADS_PER_FRAME)
|
|
732
|
+
const indexOffset = quadOffset * INDICES_PER_QUAD
|
|
733
|
+
const indexCount = quadCount * INDICES_PER_QUAD
|
|
734
|
+
|
|
735
|
+
const indexAttr = this.mesh.geometry.index as THREE.BufferAttribute
|
|
736
|
+
indexAttr.clearUpdateRanges()
|
|
737
|
+
indexAttr.addUpdateRange(indexOffset, indexCount)
|
|
738
|
+
indexAttr.needsUpdate = true
|
|
739
|
+
|
|
740
|
+
if (quadOffset + quadCount > r.end) {
|
|
741
|
+
this.indexPendingRanges.shift()
|
|
742
|
+
} else {
|
|
743
|
+
r.start = quadOffset + quadCount
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
616
747
|
setRenderOrigin(renderOrigin: RenderOrigin): void {
|
|
617
748
|
this.renderOrigin = { ...renderOrigin }
|
|
618
749
|
}
|
|
@@ -637,6 +768,10 @@ export class GlobalLegacyBuffer {
|
|
|
637
768
|
}
|
|
638
769
|
|
|
639
770
|
setCameraOrigin(x: number, y: number, z: number): void {
|
|
771
|
+
this._camX = x
|
|
772
|
+
this._camY = y
|
|
773
|
+
this._camZ = z
|
|
774
|
+
this._camInitialized = true
|
|
640
775
|
const { originDelta, cameraOriginFrac } = computeCameraRelativeUniforms(this.renderOrigin, x, y, z)
|
|
641
776
|
const u = this.material.uniforms.u_originDelta
|
|
642
777
|
if (u?.value?.set) u.value.set(originDelta.x, originDelta.y, originDelta.z)
|
|
@@ -669,6 +804,7 @@ export class GlobalLegacyBuffer {
|
|
|
669
804
|
this.freeList.length = 0
|
|
670
805
|
this.highWatermark = 0
|
|
671
806
|
this.pendingRanges.length = 0
|
|
807
|
+
this.indexPendingRanges.length = 0
|
|
672
808
|
this.pendingMove = null
|
|
673
809
|
this.pendingReplace.clear()
|
|
674
810
|
this.uploadEpoch = 0
|
|
@@ -688,6 +824,29 @@ export class GlobalLegacyBuffer {
|
|
|
688
824
|
this.mergePendingRanges()
|
|
689
825
|
}
|
|
690
826
|
|
|
827
|
+
private markIndexDirty(start: number, end: number): void {
|
|
828
|
+
this.indexPendingRanges.push({ start, end })
|
|
829
|
+
this.indexPendingRanges.sort((a, b) => a.start - b.start)
|
|
830
|
+
this.mergeIndexPendingRanges()
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
private mergeIndexPendingRanges(): void {
|
|
834
|
+
if (this.indexPendingRanges.length < 2) return
|
|
835
|
+
const merged: Array<{ start: number; end: number }> = []
|
|
836
|
+
let cur = this.indexPendingRanges[0]!
|
|
837
|
+
for (let i = 1; i < this.indexPendingRanges.length; i++) {
|
|
838
|
+
const next = this.indexPendingRanges[i]!
|
|
839
|
+
if (next.start <= cur.end + 1) {
|
|
840
|
+
cur = { start: cur.start, end: Math.max(cur.end, next.end) }
|
|
841
|
+
} else {
|
|
842
|
+
merged.push(cur)
|
|
843
|
+
cur = next
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
merged.push(cur)
|
|
847
|
+
this.indexPendingRanges = merged
|
|
848
|
+
}
|
|
849
|
+
|
|
691
850
|
private mergePendingRanges(): void {
|
|
692
851
|
if (this.pendingRanges.length < 2) return
|
|
693
852
|
const merged: Array<{ start: number; end: number }> = []
|
|
@@ -835,6 +994,14 @@ export class GlobalLegacyBuffer {
|
|
|
835
994
|
for (let i = 0; i < indexLen; i++) {
|
|
836
995
|
this.indices[newIndexBase + i] = this.indices[oldIndexBase + i]! + vertDelta
|
|
837
996
|
}
|
|
997
|
+
|
|
998
|
+
const oldCentroidBase = oldStart * 3
|
|
999
|
+
const newCentroidBase = newStart * 3
|
|
1000
|
+
this.quadCentroids.copyWithin(newCentroidBase, oldCentroidBase, oldCentroidBase + quadCount * 3)
|
|
1001
|
+
|
|
1002
|
+
const oldTemplateBase = oldStart * INDICES_PER_QUAD
|
|
1003
|
+
const newTemplateBase = newStart * INDICES_PER_QUAD
|
|
1004
|
+
this.quadIndexTemplate.copyWithin(newTemplateBase, oldTemplateBase, oldTemplateBase + quadCount * INDICES_PER_QUAD)
|
|
838
1005
|
}
|
|
839
1006
|
|
|
840
1007
|
private rangeFullyUploaded(start: number, end: number): boolean {
|
|
@@ -898,6 +1065,8 @@ export class GlobalLegacyBuffer {
|
|
|
898
1065
|
const nUv = new Float32Array(newMaxVerts * FLOATS_PER_UV_VERT)
|
|
899
1066
|
const nOrigin = new Float32Array(newMaxVerts * FLOATS_PER_VERT)
|
|
900
1067
|
const nIdx = new Uint32Array(newCap * INDICES_PER_QUAD)
|
|
1068
|
+
const nCentroids = new Float32Array(newCap * 3)
|
|
1069
|
+
const nTemplate = new Uint8Array(newCap * INDICES_PER_QUAD)
|
|
901
1070
|
|
|
902
1071
|
nPos.set(this.positions)
|
|
903
1072
|
nCol.set(this.colors)
|
|
@@ -906,6 +1075,8 @@ export class GlobalLegacyBuffer {
|
|
|
906
1075
|
nUv.set(this.uvs)
|
|
907
1076
|
nOrigin.set(this.aOrigin)
|
|
908
1077
|
nIdx.set(this.indices)
|
|
1078
|
+
nCentroids.set(this.quadCentroids)
|
|
1079
|
+
nTemplate.set(this.quadIndexTemplate)
|
|
909
1080
|
|
|
910
1081
|
this.positions = nPos
|
|
911
1082
|
this.colors = nCol
|
|
@@ -914,6 +1085,8 @@ export class GlobalLegacyBuffer {
|
|
|
914
1085
|
this.uvs = nUv
|
|
915
1086
|
this.aOrigin = nOrigin
|
|
916
1087
|
this.indices = nIdx
|
|
1088
|
+
this.quadCentroids = nCentroids
|
|
1089
|
+
this.quadIndexTemplate = nTemplate
|
|
917
1090
|
this.capacityQuads = newCap
|
|
918
1091
|
|
|
919
1092
|
const geometry = this.mesh.geometry
|
|
@@ -938,5 +1111,6 @@ export class GlobalLegacyBuffer {
|
|
|
938
1111
|
geometry.setIndex(indexAttr)
|
|
939
1112
|
|
|
940
1113
|
this.pendingRanges.length = 0
|
|
1114
|
+
this.indexPendingRanges.length = 0
|
|
941
1115
|
}
|
|
942
1116
|
}
|
|
@@ -228,7 +228,7 @@ export function createGlobalLegacyBlendMaterial(): THREE.ShaderMaterial {
|
|
|
228
228
|
fragmentShader,
|
|
229
229
|
uniforms: THREE.UniformsUtils.merge([THREE.UniformsLib.fog, legacyUniforms]),
|
|
230
230
|
transparent: true,
|
|
231
|
-
depthWrite:
|
|
231
|
+
depthWrite: false,
|
|
232
232
|
depthTest: true,
|
|
233
233
|
vertexColors: true,
|
|
234
234
|
glslVersion: THREE.GLSL3,
|
|
@@ -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()
|
|
@@ -760,3 +786,94 @@ test('GlobalLegacyBuffer: suppressThreeDraw uses minimal non-zero draw range', (
|
|
|
760
786
|
buffer.dispose()
|
|
761
787
|
mat.dispose()
|
|
762
788
|
})
|
|
789
|
+
|
|
790
|
+
test('GlobalLegacyBuffer: reorderSectionBlendIndices sorts quads back-to-front', () => {
|
|
791
|
+
const scene = new THREE.Scene()
|
|
792
|
+
const mat = createGlobalLegacyBlockMaterial()
|
|
793
|
+
const buffer = new GlobalLegacyBuffer(mat, scene)
|
|
794
|
+
const geo = makeTwoQuadGeometry()
|
|
795
|
+
|
|
796
|
+
buffer.addSection('blend', geo, 0, 0, 0)
|
|
797
|
+
drainUploads(buffer)
|
|
798
|
+
|
|
799
|
+
const layoutBefore = buffer.getLayoutVersion()
|
|
800
|
+
const slot = buffer.getSectionSlot('blend')!
|
|
801
|
+
const vertBase0 = slot.start * 4
|
|
802
|
+
const vertBase1 = (slot.start + 1) * 4
|
|
803
|
+
|
|
804
|
+
expect(buffer.reorderSectionBlendIndices('blend', 0, 0, -20)).toBe(true)
|
|
805
|
+
expect(buffer.getLayoutVersion()).toBe(layoutBefore)
|
|
806
|
+
|
|
807
|
+
let indices = readSectionIndices(buffer, 'blend')
|
|
808
|
+
expect(indices.slice(0, 6)).toEqual([vertBase1, vertBase1 + 2, vertBase1 + 1, vertBase1, vertBase1 + 1, vertBase1 + 2])
|
|
809
|
+
expect(indices.slice(6, 12)).toEqual([vertBase0, vertBase0 + 1, vertBase0 + 2, vertBase0, vertBase0 + 2, vertBase0 + 3])
|
|
810
|
+
|
|
811
|
+
expect(buffer.reorderSectionBlendIndices('blend', 0, 0, 20)).toBe(true)
|
|
812
|
+
indices = readSectionIndices(buffer, 'blend')
|
|
813
|
+
expect(indices.slice(0, 6)).toEqual([vertBase0, vertBase0 + 1, vertBase0 + 2, vertBase0, vertBase0 + 2, vertBase0 + 3])
|
|
814
|
+
expect(indices.slice(6, 12)).toEqual([vertBase1, vertBase1 + 2, vertBase1 + 1, vertBase1, vertBase1 + 1, vertBase1 + 2])
|
|
815
|
+
|
|
816
|
+
expect(getInternals(buffer).indexPendingRanges.length).toBeGreaterThan(0)
|
|
817
|
+
drainIndexUploads(buffer)
|
|
818
|
+
expect(buffer.hasPendingIndexUploads()).toBe(false)
|
|
819
|
+
|
|
820
|
+
buffer.dispose()
|
|
821
|
+
mat.dispose()
|
|
822
|
+
})
|
|
823
|
+
|
|
824
|
+
test('GlobalLegacyBuffer: sortBlend pre-sorts indices on addSection before upload', () => {
|
|
825
|
+
const scene = new THREE.Scene()
|
|
826
|
+
const mat = createGlobalLegacyBlockMaterial()
|
|
827
|
+
const buffer = new GlobalLegacyBuffer(mat, scene, { sortBlend: true })
|
|
828
|
+
const geo = makeTwoQuadGeometry()
|
|
829
|
+
|
|
830
|
+
buffer.setCameraOrigin(0, 0, -20)
|
|
831
|
+
buffer.addSection('blend', geo, 0, 0, 0)
|
|
832
|
+
|
|
833
|
+
const slot = buffer.getSectionSlot('blend')!
|
|
834
|
+
const vertBase0 = slot.start * 4
|
|
835
|
+
const vertBase1 = (slot.start + 1) * 4
|
|
836
|
+
const indices = readSectionIndices(buffer, 'blend')
|
|
837
|
+
|
|
838
|
+
expect(indices.slice(0, 6)).toEqual([vertBase1, vertBase1 + 2, vertBase1 + 1, vertBase1, vertBase1 + 1, vertBase1 + 2])
|
|
839
|
+
expect(indices.slice(6, 12)).toEqual([vertBase0, vertBase0 + 1, vertBase0 + 2, vertBase0, vertBase0 + 2, vertBase0 + 3])
|
|
840
|
+
expect(getInternals(buffer).indexPendingRanges.length).toBe(0)
|
|
841
|
+
|
|
842
|
+
buffer.dispose()
|
|
843
|
+
mat.dispose()
|
|
844
|
+
})
|
|
845
|
+
|
|
846
|
+
test('GlobalLegacyBuffer: blend reorder metadata survives compaction', () => {
|
|
847
|
+
const scene = new THREE.Scene()
|
|
848
|
+
const mat = createGlobalLegacyBlockMaterial()
|
|
849
|
+
const buffer = new GlobalLegacyBuffer(mat, scene)
|
|
850
|
+
const geo = makeQuadGeometry()
|
|
851
|
+
const twoGeo = makeTwoQuadGeometry()
|
|
852
|
+
|
|
853
|
+
buffer.addSection('a', geo, 0, 0, 0)
|
|
854
|
+
buffer.addSection('b', geo, 8, 0, 0)
|
|
855
|
+
buffer.addSection('blend', twoGeo, 16, 0, 0)
|
|
856
|
+
expect(buffer.getSectionSlot('blend')).toEqual({ start: 2, count: 2 })
|
|
857
|
+
|
|
858
|
+
buffer.removeSection('a')
|
|
859
|
+
buffer.removeSection('b')
|
|
860
|
+
drainUploads(buffer)
|
|
861
|
+
buffer.compactStep()
|
|
862
|
+
finishCurrentMove(buffer)
|
|
863
|
+
|
|
864
|
+
const slot = buffer.getSectionSlot('blend')!
|
|
865
|
+
expect(slot).toEqual({ start: 0, count: 2 })
|
|
866
|
+
|
|
867
|
+
const internals = getInternals(buffer)
|
|
868
|
+
expect(internals.quadCentroids[slot.start * 3 + 2]).toBeCloseTo(0, 5)
|
|
869
|
+
expect(internals.quadCentroids[(slot.start + 1) * 3 + 2]).toBeCloseTo(8, 5)
|
|
870
|
+
expect(Array.from(internals.quadIndexTemplate.slice((slot.start + 1) * 6, (slot.start + 1) * 6 + 6))).toEqual([0, 2, 1, 0, 1, 2])
|
|
871
|
+
|
|
872
|
+
expect(buffer.reorderSectionBlendIndices('blend', 0, 0, -20)).toBe(true)
|
|
873
|
+
const vertBase1 = (slot.start + 1) * 4
|
|
874
|
+
const indices = readSectionIndices(buffer, 'blend')
|
|
875
|
+
expect(indices[0]).toBe(vertBase1)
|
|
876
|
+
|
|
877
|
+
buffer.dispose()
|
|
878
|
+
mat.dispose()
|
|
879
|
+
})
|
|
@@ -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 (
|