minecraft-renderer 0.1.29 → 0.1.30
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
|
@@ -4,7 +4,6 @@ import { Vec3 } from 'vec3'
|
|
|
4
4
|
import nbt from 'prismarine-nbt'
|
|
5
5
|
import { MesherGeometryOutput, IS_FULL_WORLD_SECTION } from '../mesher/shared'
|
|
6
6
|
import { getBannerTexture, createBannerMesh, releaseBannerTexture } from './bannerRenderer'
|
|
7
|
-
import { disposeObject } from './threeJsUtils'
|
|
8
7
|
import type { WorldRendererThree } from './worldRendererThree'
|
|
9
8
|
|
|
10
9
|
export interface SectionObject extends THREE.Object3D {
|
|
@@ -17,6 +16,9 @@ export class WorldBlockGeometry {
|
|
|
17
16
|
sectionObjects: Record<string, SectionObject> = {}
|
|
18
17
|
waitingChunksToDisplay: { [chunkKey: string]: string[] } = {}
|
|
19
18
|
estimatedMemoryUsage = 0
|
|
19
|
+
private pendingUpdates: Map<string, { geometry: MesherGeometryOutput; key: string; type: string }> = new Map()
|
|
20
|
+
private pendingBufferStartTime: number | null = null
|
|
21
|
+
private static readonly MAX_BUFFER_MS = 500
|
|
20
22
|
|
|
21
23
|
constructor(
|
|
22
24
|
private readonly worldRenderer: WorldRendererThree,
|
|
@@ -26,21 +28,84 @@ export class WorldBlockGeometry {
|
|
|
26
28
|
) { }
|
|
27
29
|
|
|
28
30
|
handleWorkerGeometryMessage(data: { geometry: MesherGeometryOutput; key: string; type: string }): void {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
const isUpdate = !!this.sectionObjects[data.key]
|
|
32
|
+
|
|
33
|
+
if (isUpdate) {
|
|
34
|
+
// Buffer updates for existing sections — keep old mesh visible
|
|
35
|
+
this.pendingUpdates.set(data.key, data)
|
|
36
|
+
if (this.pendingBufferStartTime === null) {
|
|
37
|
+
this.pendingBufferStartTime = performance.now()
|
|
38
|
+
}
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Initial load — apply immediately
|
|
43
|
+
this._applySectionGeometry(data)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
applyPendingUpdates(): void {
|
|
47
|
+
if (this.pendingUpdates.size === 0) return
|
|
48
|
+
|
|
49
|
+
const now = performance.now()
|
|
50
|
+
const sinceFirst = now - (this.pendingBufferStartTime ?? now)
|
|
51
|
+
|
|
52
|
+
// Wait for neighboring sections still being meshed (unless max timeout reached)
|
|
53
|
+
if (sinceFirst < WorldBlockGeometry.MAX_BUFFER_MS) {
|
|
54
|
+
const sectionHeight = this.worldRenderer.getSectionHeight()
|
|
55
|
+
for (const key of this.pendingUpdates.keys()) {
|
|
56
|
+
const [sx, sy, sz] = key.split(',').map(Number)
|
|
57
|
+
const neighborKeys = [
|
|
58
|
+
`${sx - 16},${sy},${sz}`, `${sx + 16},${sy},${sz}`,
|
|
59
|
+
`${sx},${sy - sectionHeight},${sz}`, `${sx},${sy + sectionHeight},${sz}`,
|
|
60
|
+
`${sx},${sy},${sz - 16}`, `${sx},${sy},${sz + 16}`,
|
|
61
|
+
]
|
|
62
|
+
for (const neighborKey of neighborKeys) {
|
|
63
|
+
// Wait if neighbor is being meshed, hasn't arrived yet, and has a loaded mesh
|
|
64
|
+
if (this.worldRenderer.sectionsWaiting.has(neighborKey) &&
|
|
65
|
+
!this.pendingUpdates.has(neighborKey) &&
|
|
66
|
+
this.sectionObjects[neighborKey]) {
|
|
67
|
+
return
|
|
68
|
+
}
|
|
37
69
|
}
|
|
38
|
-
}
|
|
39
|
-
this.worldRenderer.sceneOrigin.removeAndUntrackAll(object)
|
|
40
|
-
disposeObject(object)
|
|
41
|
-
delete this.sectionObjects[data.key]
|
|
70
|
+
}
|
|
42
71
|
}
|
|
43
72
|
|
|
73
|
+
// Flush all pending updates atomically
|
|
74
|
+
for (const [key, data] of this.pendingUpdates) {
|
|
75
|
+
this._removeSectionSafely(key)
|
|
76
|
+
this._applySectionGeometry(data)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this.pendingUpdates.clear()
|
|
80
|
+
this.pendingBufferStartTime = null
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private disposeSectionObject(obj: THREE.Object3D): void {
|
|
84
|
+
if (obj instanceof THREE.Mesh) {
|
|
85
|
+
obj.geometry?.dispose?.()
|
|
86
|
+
// Don't dispose material - it's shared across all sections
|
|
87
|
+
}
|
|
88
|
+
if (obj.children) {
|
|
89
|
+
obj.children.forEach(child => this.disposeSectionObject(child))
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private _removeSectionSafely(key: string): void {
|
|
94
|
+
const object = this.sectionObjects[key]
|
|
95
|
+
if (!object) return
|
|
96
|
+
|
|
97
|
+
this.removeSectionMemoryUsage(object)
|
|
98
|
+
object.traverse((child) => {
|
|
99
|
+
if ((child as any).bannerTexture) {
|
|
100
|
+
releaseBannerTexture((child as any).bannerTexture)
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
this.worldRenderer.sceneOrigin.removeAndUntrackAll(object)
|
|
104
|
+
this.disposeSectionObject(object)
|
|
105
|
+
delete this.sectionObjects[key]
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private _applySectionGeometry(data: { geometry: MesherGeometryOutput; key: string; type: string }): void {
|
|
44
109
|
const chunkCoords = data.key.split(',')
|
|
45
110
|
if (
|
|
46
111
|
!this.worldRenderer.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] ||
|
|
@@ -69,7 +134,7 @@ export class WorldBlockGeometry {
|
|
|
69
134
|
this.worldRenderer.sceneOrigin.track(mesh, { updateMatrix: true })
|
|
70
135
|
mesh.position.set(data.geometry.sx, data.geometry.sy, data.geometry.sz)
|
|
71
136
|
mesh.name = 'mesh'
|
|
72
|
-
object = new THREE.Group()
|
|
137
|
+
const object: THREE.Object3D = new THREE.Group()
|
|
73
138
|
object.add(mesh)
|
|
74
139
|
// mesh with static dimensions: 16x16xsectionHeight
|
|
75
140
|
const sectionHeight = data.geometry.sectionEndY - data.geometry.sectionStartY
|
|
@@ -238,6 +303,8 @@ export class WorldBlockGeometry {
|
|
|
238
303
|
|
|
239
304
|
// Reset memory tracking since all sections are cleared
|
|
240
305
|
this.estimatedMemoryUsage = 0
|
|
306
|
+
this.pendingUpdates.clear()
|
|
307
|
+
this.pendingBufferStartTime = null
|
|
241
308
|
}
|
|
242
309
|
|
|
243
310
|
removeColumn(x: number, z: number) {
|
|
@@ -259,9 +326,10 @@ export class WorldBlockGeometry {
|
|
|
259
326
|
}
|
|
260
327
|
})
|
|
261
328
|
this.worldRenderer.sceneOrigin.removeAndUntrackAll(mesh)
|
|
262
|
-
|
|
329
|
+
this.disposeSectionObject(mesh)
|
|
263
330
|
}
|
|
264
331
|
delete this.sectionObjects[key]
|
|
332
|
+
this.pendingUpdates.delete(key)
|
|
265
333
|
} else {
|
|
266
334
|
for (let y = worldMinY; y < this.worldRenderer.worldSizeParams.worldHeight; y += sectionHeight) {
|
|
267
335
|
this.worldRenderer.setSectionDirty(new Vec3(x, y, z), false)
|
|
@@ -277,9 +345,10 @@ export class WorldBlockGeometry {
|
|
|
277
345
|
}
|
|
278
346
|
})
|
|
279
347
|
this.worldRenderer.sceneOrigin.removeAndUntrackAll(mesh)
|
|
280
|
-
|
|
348
|
+
this.disposeSectionObject(mesh)
|
|
281
349
|
}
|
|
282
350
|
delete this.sectionObjects[key]
|
|
351
|
+
this.pendingUpdates.delete(key)
|
|
283
352
|
}
|
|
284
353
|
}
|
|
285
354
|
}
|
|
@@ -1065,6 +1065,8 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
1065
1065
|
|
|
1066
1066
|
// eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
|
|
1067
1067
|
const cam = this.cameraGroupVr instanceof THREE.Group ? this.cameraGroupVr.children.find(child => child instanceof THREE.PerspectiveCamera) as THREE.PerspectiveCamera : this.camera
|
|
1068
|
+
// Flush buffered geometry updates atomically before rendering
|
|
1069
|
+
this.worldBlockGeometry.applyPendingUpdates()
|
|
1068
1070
|
this.renderer.render(this.scene, cam)
|
|
1069
1071
|
|
|
1070
1072
|
if (
|