minecraft-renderer 0.1.34 → 0.1.35
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 +55 -55
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +363 -363
- package/package.json +1 -1
- package/src/graphicsBackend/config.ts +2 -1
- package/src/lib/worldrendererCommon.ts +2 -0
- package/src/playground/scenes/main.ts +1 -1
- package/src/three/chunkMeshManager.ts +808 -0
- package/src/three/entities.ts +42 -36
- package/src/three/graphicsBackendBase.ts +1 -1
- package/src/three/modules/sciFiWorldReveal.ts +10 -21
- package/src/three/panorama.ts +1 -1
- package/src/three/worldRendererThree.ts +60 -39
- package/src/three/worldBlockGeometry.ts +0 -355
|
@@ -1,355 +0,0 @@
|
|
|
1
|
-
//@ts-nocheck
|
|
2
|
-
import * as THREE from 'three'
|
|
3
|
-
import { Vec3 } from 'vec3'
|
|
4
|
-
import nbt from 'prismarine-nbt'
|
|
5
|
-
import { MesherGeometryOutput, IS_FULL_WORLD_SECTION } from '../mesher/shared'
|
|
6
|
-
import { getBannerTexture, createBannerMesh, releaseBannerTexture } from './bannerRenderer'
|
|
7
|
-
import type { WorldRendererThree } from './worldRendererThree'
|
|
8
|
-
|
|
9
|
-
export interface SectionObject extends THREE.Object3D {
|
|
10
|
-
foutain?: boolean
|
|
11
|
-
tilesCount?: number
|
|
12
|
-
blocksCount?: number
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export class WorldBlockGeometry {
|
|
16
|
-
sectionObjects: Record<string, SectionObject> = {}
|
|
17
|
-
waitingChunksToDisplay: { [chunkKey: string]: string[] } = {}
|
|
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
|
|
22
|
-
|
|
23
|
-
constructor(
|
|
24
|
-
private readonly worldRenderer: WorldRendererThree,
|
|
25
|
-
private readonly scene: THREE.Scene,
|
|
26
|
-
private readonly material: THREE.MeshLambertMaterial,
|
|
27
|
-
private readonly displayOptions: any
|
|
28
|
-
) { }
|
|
29
|
-
|
|
30
|
-
handleWorkerGeometryMessage(data: { geometry: MesherGeometryOutput; key: string; type: string }): void {
|
|
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
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
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 {
|
|
109
|
-
const chunkCoords = data.key.split(',')
|
|
110
|
-
if (
|
|
111
|
-
!this.worldRenderer.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] ||
|
|
112
|
-
!data.geometry.positions.length ||
|
|
113
|
-
!this.worldRenderer.active
|
|
114
|
-
)
|
|
115
|
-
return
|
|
116
|
-
|
|
117
|
-
const geometry = new THREE.BufferGeometry()
|
|
118
|
-
const positionAttr = new THREE.BufferAttribute(data.geometry.positions, 3)
|
|
119
|
-
const normalAttr = new THREE.BufferAttribute(data.geometry.normals, 3)
|
|
120
|
-
const colorAttr = new THREE.BufferAttribute(data.geometry.colors, 3)
|
|
121
|
-
const uvAttr = new THREE.BufferAttribute(data.geometry.uvs, 2)
|
|
122
|
-
const indexAttr = new THREE.BufferAttribute(data.geometry.indices as Uint32Array | Uint16Array, 1)
|
|
123
|
-
|
|
124
|
-
geometry.setAttribute('position', positionAttr)
|
|
125
|
-
geometry.setAttribute('normal', normalAttr)
|
|
126
|
-
geometry.setAttribute('color', colorAttr)
|
|
127
|
-
geometry.setAttribute('uv', uvAttr)
|
|
128
|
-
geometry.index = indexAttr
|
|
129
|
-
|
|
130
|
-
// Track memory usage for this section
|
|
131
|
-
this.addSectionMemoryUsage(geometry)
|
|
132
|
-
|
|
133
|
-
const mesh = new THREE.Mesh(geometry, this.material)
|
|
134
|
-
this.worldRenderer.sceneOrigin.track(mesh, { updateMatrix: true })
|
|
135
|
-
mesh.position.set(data.geometry.sx, data.geometry.sy, data.geometry.sz)
|
|
136
|
-
mesh.name = 'mesh'
|
|
137
|
-
const object: THREE.Object3D = new THREE.Group()
|
|
138
|
-
object.add(mesh)
|
|
139
|
-
// mesh with static dimensions: 16x16xsectionHeight
|
|
140
|
-
const sectionHeight = data.geometry.sectionEndY - data.geometry.sectionStartY
|
|
141
|
-
const CHUNK_SIZE = 16
|
|
142
|
-
const staticChunkMesh = new THREE.Mesh(
|
|
143
|
-
new THREE.BoxGeometry(CHUNK_SIZE, sectionHeight, CHUNK_SIZE),
|
|
144
|
-
new THREE.MeshBasicMaterial({ color: 0x00_00_00, transparent: true, opacity: 0 })
|
|
145
|
-
)
|
|
146
|
-
const boxHelper = new THREE.BoxHelper(staticChunkMesh, 0xff_ff_00)
|
|
147
|
-
boxHelper.name = 'helper'
|
|
148
|
-
this.worldRenderer.sceneOrigin.track(boxHelper, { updateMatrix: true })
|
|
149
|
-
boxHelper.position.set(data.geometry.sx, data.geometry.sy, data.geometry.sz)
|
|
150
|
-
object.add(boxHelper)
|
|
151
|
-
object.name = 'chunk'
|
|
152
|
-
; (object as any).tilesCount = data.geometry.positions.length / 3 / 4
|
|
153
|
-
; (object as any).blocksCount = data.geometry.blocksCount
|
|
154
|
-
if (!this.displayOptions.inWorldRenderingConfig.showChunkBorders) {
|
|
155
|
-
boxHelper.visible = false
|
|
156
|
-
}
|
|
157
|
-
// should not compute it once
|
|
158
|
-
if (Object.keys(data.geometry.signs).length) {
|
|
159
|
-
for (const [posKey, { isWall, isHanging, rotation }] of Object.entries(data.geometry.signs)) {
|
|
160
|
-
const signBlockEntity = this.worldRenderer.blockEntities[posKey]
|
|
161
|
-
if (!signBlockEntity) continue
|
|
162
|
-
const [x, y, z] = posKey.split(',')
|
|
163
|
-
const sign = this.worldRenderer.renderSign(
|
|
164
|
-
new Vec3(+x, +y, +z),
|
|
165
|
-
rotation,
|
|
166
|
-
isWall,
|
|
167
|
-
isHanging,
|
|
168
|
-
nbt.simplify(signBlockEntity)
|
|
169
|
-
)
|
|
170
|
-
if (!sign) continue
|
|
171
|
-
object.add(sign)
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
if (Object.keys(data.geometry.heads).length) {
|
|
175
|
-
for (const [posKey, { isWall, rotation }] of Object.entries(data.geometry.heads)) {
|
|
176
|
-
const headBlockEntity = this.worldRenderer.blockEntities[posKey]
|
|
177
|
-
if (!headBlockEntity) continue
|
|
178
|
-
const [x, y, z] = posKey.split(',')
|
|
179
|
-
const head = this.worldRenderer.renderHead(
|
|
180
|
-
new Vec3(+x, +y, +z),
|
|
181
|
-
rotation,
|
|
182
|
-
isWall,
|
|
183
|
-
nbt.simplify(headBlockEntity)
|
|
184
|
-
)
|
|
185
|
-
if (!head) continue
|
|
186
|
-
object.add(head)
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
if (Object.keys(data.geometry.banners).length) {
|
|
190
|
-
for (const [posKey, { isWall, rotation, blockName }] of Object.entries(data.geometry.banners)) {
|
|
191
|
-
const bannerBlockEntity = this.worldRenderer.blockEntities[posKey]
|
|
192
|
-
if (!bannerBlockEntity) continue
|
|
193
|
-
const [x, y, z] = posKey.split(',')
|
|
194
|
-
const bannerTexture = getBannerTexture(this.worldRenderer, blockName, nbt.simplify(bannerBlockEntity))
|
|
195
|
-
if (!bannerTexture) continue
|
|
196
|
-
const banner = createBannerMesh(new Vec3(+x, +y, +z), rotation, isWall, bannerTexture)
|
|
197
|
-
const { x: bwx, y: bwy, z: bwz } = banner.position
|
|
198
|
-
this.worldRenderer.sceneOrigin.track(banner)
|
|
199
|
-
banner.position.set(bwx, bwy, bwz)
|
|
200
|
-
object.add(banner)
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
this.sectionObjects[data.key] = object
|
|
204
|
-
// Store section key on object for easier lookup
|
|
205
|
-
;(object as any).sectionKey = data.key
|
|
206
|
-
if (this.displayOptions.inWorldRenderingConfig._renderByChunks) {
|
|
207
|
-
object.visible = false
|
|
208
|
-
const chunkKey = `${chunkCoords[0]},${chunkCoords[2]}`
|
|
209
|
-
this.waitingChunksToDisplay[chunkKey] ??= []
|
|
210
|
-
this.waitingChunksToDisplay[chunkKey].push(data.key)
|
|
211
|
-
if (this.worldRenderer.finishedChunks[chunkKey]) {
|
|
212
|
-
// todo it might happen even when it was not an update
|
|
213
|
-
this.finishChunk(chunkKey)
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
this.worldRenderer.updatePosDataChunk(data.key)
|
|
218
|
-
object.matrixAutoUpdate = false
|
|
219
|
-
// Force matrix update after setting camera-relative position (matrixAutoUpdate is false)
|
|
220
|
-
object.updateMatrix()
|
|
221
|
-
mesh.onAfterRender = (renderer, scene, camera, geometry, material, group) => {
|
|
222
|
-
// mesh.matrixAutoUpdate = false
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
this.scene.add(object)
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
finishChunk(chunkKey: string) {
|
|
229
|
-
for (const sectionKey of this.waitingChunksToDisplay[chunkKey] ?? []) {
|
|
230
|
-
this.sectionObjects[sectionKey].visible = true
|
|
231
|
-
}
|
|
232
|
-
delete this.waitingChunksToDisplay[chunkKey]
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Estimate memory usage of BufferGeometry attributes
|
|
238
|
-
*/
|
|
239
|
-
private estimateGeometryMemoryUsage(geometry: THREE.BufferGeometry): number {
|
|
240
|
-
let memoryBytes = 0
|
|
241
|
-
|
|
242
|
-
// Calculate memory for each attribute
|
|
243
|
-
const { attributes } = geometry
|
|
244
|
-
for (const [name, attribute] of Object.entries(attributes)) {
|
|
245
|
-
if (attribute?.array) {
|
|
246
|
-
// Each number in typed arrays takes different bytes:
|
|
247
|
-
// Float32Array: 4 bytes per number
|
|
248
|
-
// Uint32Array: 4 bytes per number
|
|
249
|
-
// Uint16Array: 2 bytes per number
|
|
250
|
-
const bytesPerElement = attribute.array.BYTES_PER_ELEMENT
|
|
251
|
-
memoryBytes += attribute.array.length * bytesPerElement
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Calculate memory for indices
|
|
256
|
-
if (geometry.index?.array) {
|
|
257
|
-
const bytesPerElement = geometry.index.array.BYTES_PER_ELEMENT
|
|
258
|
-
memoryBytes += geometry.index.array.length * bytesPerElement
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
return memoryBytes
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Update memory usage when section is added
|
|
266
|
-
*/
|
|
267
|
-
private addSectionMemoryUsage(geometry: THREE.BufferGeometry): void {
|
|
268
|
-
const memoryUsage = this.estimateGeometryMemoryUsage(geometry)
|
|
269
|
-
this.estimatedMemoryUsage += memoryUsage
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Update memory usage when section is removed
|
|
274
|
-
*/
|
|
275
|
-
private removeSectionMemoryUsage(object: THREE.Object3D): void {
|
|
276
|
-
// Find mesh with geometry in the object
|
|
277
|
-
const mesh = object.children.find((child) => child.name === 'mesh') as THREE.Mesh
|
|
278
|
-
if (mesh?.geometry) {
|
|
279
|
-
const memoryUsage = this.estimateGeometryMemoryUsage(mesh.geometry)
|
|
280
|
-
this.estimatedMemoryUsage -= memoryUsage
|
|
281
|
-
this.estimatedMemoryUsage = Math.max(0, this.estimatedMemoryUsage) // Ensure non-negative
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Get estimated memory usage in a human-readable format
|
|
287
|
-
*/
|
|
288
|
-
getEstimatedMemoryUsage(): { bytes: number; readable: string } {
|
|
289
|
-
const bytes = this.estimatedMemoryUsage
|
|
290
|
-
const mb = bytes / (1024 * 1024)
|
|
291
|
-
const readable = `${mb.toFixed(2)} MB`
|
|
292
|
-
return { bytes, readable }
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
resetWorld() {
|
|
296
|
-
for (const mesh of Object.values(this.sectionObjects)) {
|
|
297
|
-
// Track memory usage removal for all sections
|
|
298
|
-
this.removeSectionMemoryUsage(mesh)
|
|
299
|
-
this.worldRenderer.sceneOrigin.removeAndUntrackAll(mesh)
|
|
300
|
-
}
|
|
301
|
-
this.sectionObjects = {}
|
|
302
|
-
this.waitingChunksToDisplay = {}
|
|
303
|
-
|
|
304
|
-
// Reset memory tracking since all sections are cleared
|
|
305
|
-
this.estimatedMemoryUsage = 0
|
|
306
|
-
this.pendingUpdates.clear()
|
|
307
|
-
this.pendingBufferStartTime = null
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
removeColumn(x: number, z: number) {
|
|
311
|
-
const sectionHeight = this.worldRenderer.getSectionHeight()
|
|
312
|
-
const worldMinY = this.worldRenderer.worldMinYRender
|
|
313
|
-
if (IS_FULL_WORLD_SECTION) {
|
|
314
|
-
// Only one section per chunk when full world section
|
|
315
|
-
const y = worldMinY
|
|
316
|
-
this.worldRenderer.setSectionDirty(new Vec3(x, y, z), false)
|
|
317
|
-
const key = `${x},${y},${z}`
|
|
318
|
-
const mesh = this.sectionObjects[key]
|
|
319
|
-
if (mesh) {
|
|
320
|
-
// Track memory usage removal
|
|
321
|
-
this.removeSectionMemoryUsage(mesh)
|
|
322
|
-
// Cleanup banner textures before disposing
|
|
323
|
-
mesh.traverse((child) => {
|
|
324
|
-
if ((child as any).bannerTexture) {
|
|
325
|
-
releaseBannerTexture((child as any).bannerTexture)
|
|
326
|
-
}
|
|
327
|
-
})
|
|
328
|
-
this.worldRenderer.sceneOrigin.removeAndUntrackAll(mesh)
|
|
329
|
-
this.disposeSectionObject(mesh)
|
|
330
|
-
}
|
|
331
|
-
delete this.sectionObjects[key]
|
|
332
|
-
this.pendingUpdates.delete(key)
|
|
333
|
-
} else {
|
|
334
|
-
for (let y = worldMinY; y < this.worldRenderer.worldSizeParams.worldHeight; y += sectionHeight) {
|
|
335
|
-
this.worldRenderer.setSectionDirty(new Vec3(x, y, z), false)
|
|
336
|
-
const key = `${x},${y},${z}`
|
|
337
|
-
const mesh = this.sectionObjects[key]
|
|
338
|
-
if (mesh) {
|
|
339
|
-
// Track memory usage removal
|
|
340
|
-
this.removeSectionMemoryUsage(mesh)
|
|
341
|
-
// Cleanup banner textures before disposing
|
|
342
|
-
mesh.traverse((child) => {
|
|
343
|
-
if ((child as any).bannerTexture) {
|
|
344
|
-
releaseBannerTexture((child as any).bannerTexture)
|
|
345
|
-
}
|
|
346
|
-
})
|
|
347
|
-
this.worldRenderer.sceneOrigin.removeAndUntrackAll(mesh)
|
|
348
|
-
this.disposeSectionObject(mesh)
|
|
349
|
-
}
|
|
350
|
-
delete this.sectionObjects[key]
|
|
351
|
-
this.pendingUpdates.delete(key)
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|