minecraft-renderer 0.1.33 → 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.
@@ -13,7 +13,6 @@ import { addNewStat } from '../lib/ui/newStats'
13
13
  import { MesherGeometryOutput } from '../mesher/shared'
14
14
  import { ItemSpecificContextProperties } from '../playerState/types'
15
15
  import { setBlockPosition } from '../mesher/standaloneRenderer'
16
- import { getBannerTexture, createBannerMesh, releaseBannerTexture } from './bannerRenderer'
17
16
  import { getMyHand } from './hand'
18
17
  import { createHoldingBlock } from './holdingBlockFactory'
19
18
  import type { IHoldingBlock } from './holdingBlockTypes'
@@ -34,7 +33,7 @@ import { DEFAULT_TEMPERATURE, SkyboxRenderer } from './skyboxRenderer'
34
33
  import { FireworksManager } from './fireworks'
35
34
  import { SceneOrigin } from './sceneOrigin'
36
35
  import { downloadWorldGeometry } from './worldGeometryExport'
37
- import { WorldBlockGeometry } from './worldBlockGeometry'
36
+ import { ChunkMeshManager } from './chunkMeshManager'
38
37
  import type { RendererModuleManifest, RegisteredModule, RendererModuleController } from './rendererModuleSystem'
39
38
  import { BUILTIN_MODULES } from './modules/index'
40
39
 
@@ -42,16 +41,17 @@ type SectionKey = string
42
41
 
43
42
  export class WorldRendererThree extends WorldRendererCommon {
44
43
  outputFormat = 'threeJs' as const
45
- worldBlockGeometry: WorldBlockGeometry
44
+ chunkMeshManager: ChunkMeshManager
46
45
  get sectionObjects() {
47
- return this.worldBlockGeometry.sectionObjects
46
+ return this.chunkMeshManager.sectionObjects
48
47
  }
49
48
  chunkTextures = new Map<string, { [pos: string]: THREE.Texture }>()
50
49
  signsCache = new Map<string, any>()
51
50
  cameraSectionPos: Vec3 = new Vec3(0, 0, 0)
52
51
  holdingBlock: IHoldingBlock
53
52
  holdingBlockLeft: IHoldingBlock
54
- scene = new THREE.Scene()
53
+ realScene = new THREE.Scene()
54
+ scene = new THREE.Group()
55
55
  ambientLight = new THREE.AmbientLight(0xcc_cc_cc)
56
56
  directionalLight = new THREE.DirectionalLight(0xff_ff_ff, 0.5)
57
57
  entities = new Entities(this, (globalThis as any).mcData)
@@ -65,7 +65,7 @@ export class WorldRendererThree extends WorldRendererCommon {
65
65
  cameraContainer!: THREE.Object3D
66
66
  media: ThreeJsMedia
67
67
  get waitingChunksToDisplay() {
68
- return this.worldBlockGeometry.waitingChunksToDisplay
68
+ return {} as { [chunkKey: string]: string[] }
69
69
  }
70
70
  waypoints: WaypointsRenderer
71
71
  cinimaticScript: CinimaticScriptRunner
@@ -81,7 +81,7 @@ export class WorldRendererThree extends WorldRendererCommon {
81
81
  renderTimeAvg = 0
82
82
  // Memory usage tracking (in bytes)
83
83
  get estimatedMemoryUsage() {
84
- return this.worldBlockGeometry.estimatedMemoryUsage
84
+ return this.chunkMeshManager.getEstimatedMemoryUsage().total
85
85
  }
86
86
  // Module system
87
87
  private modules = {} as Record<string, RegisteredModule>
@@ -107,7 +107,7 @@ export class WorldRendererThree extends WorldRendererCommon {
107
107
  DEBUG_RAYCAST = false
108
108
  skyboxRenderer: SkyboxRenderer
109
109
  fireworks: FireworksManager
110
- sceneOrigin = new SceneOrigin(this.scene)
110
+ sceneOrigin = new SceneOrigin(this.realScene)
111
111
  /** Camera world position stored in float64 (JS number) for precision */
112
112
  cameraWorldPos = { x: 0, y: 0, z: 0 }
113
113
 
@@ -131,11 +131,11 @@ export class WorldRendererThree extends WorldRendererCommon {
131
131
  private readonly _tpChunkWorldPos = new THREE.Vector3()
132
132
 
133
133
  get tilesRendered() {
134
- return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).tilesCount, 0)
134
+ return this.chunkMeshManager.getTotalTiles()
135
135
  }
136
136
 
137
137
  get blocksRendered() {
138
- return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).blocksCount, 0)
138
+ return this.chunkMeshManager.getTotalBlocks()
139
139
  }
140
140
 
141
141
  constructor(public renderer: THREE.WebGLRenderer, public initOptions: GraphicsInitOptions, public displayOptions: DisplayWorldOptions) {
@@ -145,8 +145,11 @@ export class WorldRendererThree extends WorldRendererCommon {
145
145
  this.renderer = renderer
146
146
  displayOptions.rendererState.renderer = WorldRendererThree.getRendererInfo(renderer) ?? '...'
147
147
 
148
- // Initialize world block geometry handler
149
- this.worldBlockGeometry = new WorldBlockGeometry(this, this.scene, this.material, displayOptions)
148
+ // Initialize chunk mesh manager
149
+ this.chunkMeshManager = new ChunkMeshManager(this, this.scene, this.material, this.worldSizeParams.worldHeight, this.viewDistance)
150
+ this.onRenderDistanceChanged = (viewDistance) => {
151
+ this.chunkMeshManager.updateViewDistance(viewDistance)
152
+ }
150
153
 
151
154
  this.cursorBlock = new CursorBlock(this)
152
155
  this.holdingBlock = createHoldingBlock(this)
@@ -158,7 +161,7 @@ export class WorldRendererThree extends WorldRendererCommon {
158
161
  }
159
162
 
160
163
  // Initialize skybox renderer
161
- this.skyboxRenderer = new SkyboxRenderer(this.scene, false, null)
164
+ this.skyboxRenderer = new SkyboxRenderer(this.realScene, false, null)
162
165
  void this.skyboxRenderer.init()
163
166
 
164
167
  this.addDebugOverlay()
@@ -181,7 +184,7 @@ export class WorldRendererThree extends WorldRendererCommon {
181
184
  fov: this.camera.fov
182
185
  })
183
186
  )
184
- this.fireworks = new FireworksManager(this.scene, this.sceneOrigin)
187
+ this.fireworks = new FireworksManager(this.realScene, this.sceneOrigin)
185
188
 
186
189
  // this.fountain = new Fountain(this.scene, this.scene, {
187
190
  // position: new THREE.Vector3(0, 10, 0),
@@ -447,19 +450,20 @@ export class WorldRendererThree extends WorldRendererCommon {
447
450
  this.cameraWorldPos.y = 0
448
451
  this.cameraWorldPos.z = 0
449
452
 
450
- this.scene.matrixAutoUpdate = false // for perf
451
- this.scene.background = new THREE.Color(this.initOptions.config.sceneBackground)
452
- this.scene.add(this.ambientLight)
453
+ this.realScene.matrixAutoUpdate = false // for perf
454
+ this.realScene.background = new THREE.Color(this.initOptions.config.sceneBackground)
455
+ this.realScene.add(this.ambientLight)
453
456
  this.directionalLight.position.set(1, 1, 0.5).normalize()
454
457
  this.directionalLight.castShadow = true
455
- this.scene.add(this.directionalLight)
458
+ this.realScene.add(this.directionalLight)
456
459
 
457
460
  const size = this.renderer.getSize(new THREE.Vector2())
458
461
  this.camera = new THREE.PerspectiveCamera(75, size.x / size.y, 0.1, 1000)
459
462
  this._wrapCameraPositionWithWarning()
460
463
  this.cameraContainer = new THREE.Object3D()
461
464
  this.cameraContainer.add(this.camera)
462
- this.scene.add(this.cameraContainer)
465
+ this.realScene.add(this.cameraContainer)
466
+ this.realScene.add(this.scene)
463
467
  }
464
468
 
465
469
  override watchReactivePlayerState() {
@@ -625,7 +629,7 @@ export class WorldRendererThree extends WorldRendererCommon {
625
629
  }
626
630
 
627
631
  changeBackgroundColor(color: [number, number, number]): void {
628
- this.scene.background = new THREE.Color(color[0], color[1], color[2])
632
+ this.realScene.background = new THREE.Color(color[0], color[1], color[2])
629
633
  }
630
634
 
631
635
  timeUpdated(newTime: number): void {
@@ -662,7 +666,7 @@ export class WorldRendererThree extends WorldRendererCommon {
662
666
  setBlockPosition(mesh, pos)
663
667
  const helper = new THREE.BoxHelper(mesh, 0xff_ff_00)
664
668
  mesh.add(helper)
665
- this.scene.add(mesh)
669
+ this.realScene.add(mesh)
666
670
  }
667
671
 
668
672
  demoItem() {
@@ -675,7 +679,7 @@ export class WorldRendererThree extends WorldRendererCommon {
675
679
  // mesh.scale.set(0.5, 0.5, 0.5)
676
680
  const helper = new THREE.BoxHelper(mesh, 0xff_ff_00)
677
681
  mesh.add(helper)
678
- this.scene.add(mesh)
682
+ this.realScene.add(mesh)
679
683
  }
680
684
 
681
685
  debugOverlayAdded = false
@@ -695,7 +699,9 @@ export class WorldRendererThree extends WorldRendererCommon {
695
699
  text += `TE: ${formatBigNumber(this.renderer.info.memory.textures)} `
696
700
  text += `F: ${formatBigNumber(this.tilesRendered)} `
697
701
  text += `B: ${formatBigNumber(this.blocksRendered)} `
698
- text += `MEM: ${this.worldBlockGeometry.getEstimatedMemoryUsage().readable}`
702
+ text += `MEM: ${this.chunkMeshManager.getEstimatedMemoryUsage().total} `
703
+ const poolStats = this.chunkMeshManager.getStats()
704
+ text += `POOL: ${poolStats.activeCount}/${poolStats.poolSize} HR: ${poolStats.hitRate}`
699
705
  pane.updateText(text)
700
706
  this.backendInfoReport = text
701
707
  }
@@ -709,7 +715,8 @@ export class WorldRendererThree extends WorldRendererCommon {
709
715
  const [x, y, z] = key.split(',').map(x => Math.floor(+x / 16))
710
716
  // sum of distances: x + y + z
711
717
  const chunkDistance = Math.abs(x - this.cameraSectionPos.x) + Math.abs(y - this.cameraSectionPos.y) + Math.abs(z - this.cameraSectionPos.z)
712
- const section = this.sectionObjects[key].children.find(child => child.name === 'mesh')!
718
+ const sectionObj = this.sectionObjects[key]
719
+ const section = (sectionObj as any).mesh ?? sectionObj.children.find(child => child.name === 'mesh')!
713
720
  section.renderOrder = 500 - chunkDistance
714
721
  }
715
722
 
@@ -732,12 +739,15 @@ export class WorldRendererThree extends WorldRendererCommon {
732
739
  }
733
740
 
734
741
  finishChunk(chunkKey: string) {
735
- this.worldBlockGeometry.finishChunk(chunkKey)
742
+ // ChunkMeshManager applies updates immediately, no buffering needed
736
743
  }
737
744
 
738
745
  handleWorkerMessage(data: { geometry: MesherGeometryOutput, key, type }): void {
739
746
  if (data.type === 'geometry') {
740
- this.worldBlockGeometry.handleWorkerGeometryMessage(data)
747
+ const chunkCoords = data.key.split(',')
748
+ if (!this.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] || !data.geometry.positions.length || !this.active) return
749
+ this.chunkMeshManager.updateSection(data.key, data.geometry)
750
+ this.updatePosDataChunk(data.key)
741
751
  }
742
752
  }
743
753
 
@@ -883,11 +893,11 @@ export class WorldRendererThree extends WorldRendererCommon {
883
893
  private debugRaycast(pos: THREE.Vector3, direction: THREE.Vector3, distance: number) {
884
894
  // Remove existing debug objects
885
895
  if (this.debugRaycastHelper) {
886
- this.scene.remove(this.debugRaycastHelper)
896
+ this.realScene.remove(this.debugRaycastHelper)
887
897
  this.debugRaycastHelper = undefined
888
898
  }
889
899
  if (this.debugHitPoint) {
890
- this.scene.remove(this.debugHitPoint)
900
+ this.realScene.remove(this.debugHitPoint)
891
901
  this.debugHitPoint = undefined
892
902
  }
893
903
 
@@ -907,14 +917,14 @@ export class WorldRendererThree extends WorldRendererCommon {
907
917
  distance * 0.1,
908
918
  distance * 0.05
909
919
  )
910
- this.scene.add(this.debugRaycastHelper)
920
+ this.realScene.add(this.debugRaycastHelper)
911
921
 
912
922
  // Create hit point indicator
913
923
  const hitGeometry = new THREE.SphereGeometry(0.2, 8, 8)
914
924
  const hitMaterial = new THREE.MeshBasicMaterial({ color: 0x00_ff_00 })
915
925
  this.debugHitPoint = new THREE.Mesh(hitGeometry, hitMaterial)
916
926
  this.debugHitPoint.position.copy(scenePos).add(direction.clone().multiplyScalar(distance))
917
- this.scene.add(this.debugHitPoint)
927
+ this.realScene.add(this.debugHitPoint)
918
928
  }
919
929
 
920
930
  prevFramePerspective = null as string | null
@@ -1012,11 +1022,11 @@ export class WorldRendererThree extends WorldRendererCommon {
1012
1022
 
1013
1023
  // remove any debug raycasting
1014
1024
  if (this.debugRaycastHelper) {
1015
- this.scene.remove(this.debugRaycastHelper)
1025
+ this.realScene.remove(this.debugRaycastHelper)
1016
1026
  this.debugRaycastHelper = undefined
1017
1027
  }
1018
1028
  if (this.debugHitPoint) {
1019
- this.scene.remove(this.debugHitPoint)
1029
+ this.realScene.remove(this.debugHitPoint)
1020
1030
  this.debugHitPoint = undefined
1021
1031
  }
1022
1032
  }
@@ -1087,9 +1097,8 @@ export class WorldRendererThree extends WorldRendererCommon {
1087
1097
 
1088
1098
  // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
1089
1099
  const cam = this.cameraGroupVr instanceof THREE.Group ? this.cameraGroupVr.children.find(child => child instanceof THREE.PerspectiveCamera) as THREE.PerspectiveCamera : this.camera
1090
- // Flush buffered geometry updates atomically before rendering
1091
- this.worldBlockGeometry.applyPendingUpdates()
1092
- this.renderer.render(this.scene, cam)
1100
+ // ChunkMeshManager applies updates immediately, no pending updates to flush
1101
+ this.renderer.render(this.realScene, cam)
1093
1102
 
1094
1103
  if (
1095
1104
  this.displayOptions.inWorldRenderingConfig.showHand &&
@@ -1118,6 +1127,9 @@ export class WorldRendererThree extends WorldRendererCommon {
1118
1127
  }
1119
1128
  const end = performance.now()
1120
1129
  const totalTime = end - start
1130
+ if (this.worldRendererConfig.autoLowerRenderDistance) {
1131
+ this.chunkMeshManager.recordRenderTime(totalTime)
1132
+ }
1121
1133
  this.renderTimeAvgCount++
1122
1134
  this.renderTimeAvg = ((this.renderTimeAvg * (this.renderTimeAvgCount - 1)) + totalTime) / this.renderTimeAvgCount
1123
1135
  this.renderTimeMax = Math.max(this.renderTimeMax, totalTime)
@@ -1233,15 +1245,16 @@ export class WorldRendererThree extends WorldRendererCommon {
1233
1245
  resetWorld() {
1234
1246
  super.resetWorld()
1235
1247
 
1236
- this.worldBlockGeometry.resetWorld()
1248
+ this.chunkMeshManager.dispose()
1249
+ this.chunkMeshManager = new ChunkMeshManager(this, this.scene, this.material, this.worldSizeParams.worldHeight, this.viewDistance)
1237
1250
 
1238
1251
  // Clean up debug objects
1239
1252
  if (this.debugRaycastHelper) {
1240
- this.scene.remove(this.debugRaycastHelper)
1253
+ this.realScene.remove(this.debugRaycastHelper)
1241
1254
  this.debugRaycastHelper = undefined
1242
1255
  }
1243
1256
  if (this.debugHitPoint) {
1244
- this.scene.remove(this.debugHitPoint)
1257
+ this.realScene.remove(this.debugHitPoint)
1245
1258
  this.debugHitPoint = undefined
1246
1259
  }
1247
1260
  }
@@ -1286,7 +1299,14 @@ export class WorldRendererThree extends WorldRendererCommon {
1286
1299
  super.removeColumn(x, z)
1287
1300
 
1288
1301
  this.cleanChunkTextures(x, z)
1289
- this.worldBlockGeometry.removeColumn(x, z)
1302
+ const sectionHeight = this.getSectionHeight()
1303
+ const worldMinY = this.worldMinYRender
1304
+ for (let y = worldMinY; y < this.worldSizeParams.worldHeight; y += sectionHeight) {
1305
+ const key = `${x},${y},${z}`
1306
+ if (this.chunkMeshManager.sectionObjects[key]) {
1307
+ this.chunkMeshManager.releaseSection(key)
1308
+ }
1309
+ }
1290
1310
  }
1291
1311
 
1292
1312
  setSectionDirty(...args: Parameters<WorldRendererCommon['setSectionDirty']>) {
@@ -1309,6 +1329,7 @@ export class WorldRendererThree extends WorldRendererCommon {
1309
1329
  }
1310
1330
 
1311
1331
  destroy(): void {
1332
+ this.chunkMeshManager.dispose()
1312
1333
  this.disposeModules()
1313
1334
  this.fireworksLegacy.destroy()
1314
1335
  super.destroy()
@@ -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
- }