minecraft-renderer 0.1.0

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.
Files changed (187) hide show
  1. package/README.md +297 -0
  2. package/dist/index.html +83 -0
  3. package/dist/static/image/arrow.6f27b59f.png +0 -0
  4. package/dist/static/image/blocksAtlasLatest.7850afa3.png +0 -0
  5. package/dist/static/image/blocksAtlasLegacy.5c76823d.png +0 -0
  6. package/dist/static/image/itemsAtlasLatest.36036f95.png +0 -0
  7. package/dist/static/image/itemsAtlasLegacy.dcb1b58d.png +0 -0
  8. package/dist/static/image/tipped_arrow.6f27b59f.png +0 -0
  9. package/dist/static/js/365.f05233ab.js +8462 -0
  10. package/dist/static/js/365.f05233ab.js.LICENSE.txt +52 -0
  11. package/dist/static/js/async/738.efa27644.js +1 -0
  12. package/dist/static/js/index.092ec5be.js +56 -0
  13. package/dist/static/js/lib-polyfill.98986ac5.js +1 -0
  14. package/dist/static/js/lib-react.5c9129e0.js +2 -0
  15. package/dist/static/js/lib-react.5c9129e0.js.LICENSE.txt +39 -0
  16. package/package.json +104 -0
  17. package/src/assets/destroy_stage_0.png +0 -0
  18. package/src/assets/destroy_stage_1.png +0 -0
  19. package/src/assets/destroy_stage_2.png +0 -0
  20. package/src/assets/destroy_stage_3.png +0 -0
  21. package/src/assets/destroy_stage_4.png +0 -0
  22. package/src/assets/destroy_stage_5.png +0 -0
  23. package/src/assets/destroy_stage_6.png +0 -0
  24. package/src/assets/destroy_stage_7.png +0 -0
  25. package/src/assets/destroy_stage_8.png +0 -0
  26. package/src/assets/destroy_stage_9.png +0 -0
  27. package/src/examples/README.md +146 -0
  28. package/src/examples/appViewerExample.ts +205 -0
  29. package/src/examples/initialMenuStart.ts +161 -0
  30. package/src/graphicsBackend/appViewer.ts +297 -0
  31. package/src/graphicsBackend/config.ts +119 -0
  32. package/src/graphicsBackend/index.ts +10 -0
  33. package/src/graphicsBackend/playerState.ts +61 -0
  34. package/src/graphicsBackend/types.ts +143 -0
  35. package/src/index.ts +97 -0
  36. package/src/lib/DebugGui.ts +190 -0
  37. package/src/lib/animationController.ts +85 -0
  38. package/src/lib/buildSharedConfig.mjs +1 -0
  39. package/src/lib/cameraBobbing.ts +94 -0
  40. package/src/lib/canvas2DOverlay.example.ts +361 -0
  41. package/src/lib/canvas2DOverlay.quickstart.ts +242 -0
  42. package/src/lib/canvas2DOverlay.ts +381 -0
  43. package/src/lib/cleanupDecorator.ts +29 -0
  44. package/src/lib/createPlayerObject.ts +55 -0
  45. package/src/lib/frameTimingCollector.ts +164 -0
  46. package/src/lib/guiRenderer.ts +283 -0
  47. package/src/lib/items.ts +140 -0
  48. package/src/lib/mesherlogReader.ts +131 -0
  49. package/src/lib/moreBlockDataGenerated.json +714 -0
  50. package/src/lib/preflatMap.json +1741 -0
  51. package/src/lib/simpleUtils.ts +40 -0
  52. package/src/lib/smoothSwitcher.ts +168 -0
  53. package/src/lib/spiral.ts +29 -0
  54. package/src/lib/ui/newStats.ts +120 -0
  55. package/src/lib/utils/proxy.ts +23 -0
  56. package/src/lib/utils/skins.ts +63 -0
  57. package/src/lib/utils.ts +76 -0
  58. package/src/lib/workerProxy.ts +342 -0
  59. package/src/lib/worldrendererCommon.ts +1088 -0
  60. package/src/mesher/mesher.ts +253 -0
  61. package/src/mesher/models.ts +769 -0
  62. package/src/mesher/modelsGeometryCommon.ts +142 -0
  63. package/src/mesher/shared.ts +80 -0
  64. package/src/mesher/standaloneRenderer.ts +270 -0
  65. package/src/mesher/test/a.ts +3 -0
  66. package/src/mesher/test/mesherTester.ts +76 -0
  67. package/src/mesher/test/playground.ts +19 -0
  68. package/src/mesher/test/test-perf.ts +74 -0
  69. package/src/mesher/test/tests.test.ts +56 -0
  70. package/src/mesher/world.ts +294 -0
  71. package/src/mesher/worldConstants.ts +1 -0
  72. package/src/modules/index.ts +11 -0
  73. package/src/modules/starfield.ts +313 -0
  74. package/src/modules/types.ts +110 -0
  75. package/src/playerState/playerState.ts +78 -0
  76. package/src/playerState/types.ts +36 -0
  77. package/src/playground/allEntitiesDebug.ts +170 -0
  78. package/src/playground/baseScene.ts +587 -0
  79. package/src/playground/mobileControls.tsx +268 -0
  80. package/src/playground/playground.html +83 -0
  81. package/src/playground/playground.ts +11 -0
  82. package/src/playground/playgroundUi.tsx +140 -0
  83. package/src/playground/reactUtils.ts +71 -0
  84. package/src/playground/scenes/allEntities.ts +13 -0
  85. package/src/playground/scenes/entities.ts +37 -0
  86. package/src/playground/scenes/floorRandom.ts +33 -0
  87. package/src/playground/scenes/frequentUpdates.ts +148 -0
  88. package/src/playground/scenes/geometryExport.ts +142 -0
  89. package/src/playground/scenes/index.ts +12 -0
  90. package/src/playground/scenes/lightingStarfield.ts +40 -0
  91. package/src/playground/scenes/main.ts +313 -0
  92. package/src/playground/scenes/railsCobweb.ts +14 -0
  93. package/src/playground/scenes/rotationIssue.ts +7 -0
  94. package/src/playground/scenes/slabsOptimization.ts +16 -0
  95. package/src/playground/scenes/transparencyIssue.ts +11 -0
  96. package/src/playground/shared.ts +79 -0
  97. package/src/resourcesManager/index.ts +5 -0
  98. package/src/resourcesManager/resourcesManager.ts +314 -0
  99. package/src/shims/minecraftData.ts +41 -0
  100. package/src/sign-renderer/index.html +21 -0
  101. package/src/sign-renderer/index.ts +216 -0
  102. package/src/sign-renderer/noop.js +1 -0
  103. package/src/sign-renderer/playground.ts +38 -0
  104. package/src/sign-renderer/tests.test.ts +69 -0
  105. package/src/sign-renderer/vite.config.ts +10 -0
  106. package/src/three/appShared.ts +75 -0
  107. package/src/three/bannerRenderer.ts +275 -0
  108. package/src/three/cameraShake.ts +120 -0
  109. package/src/three/cinimaticScript.ts +350 -0
  110. package/src/three/documentRenderer.ts +491 -0
  111. package/src/three/entities.ts +1580 -0
  112. package/src/three/entity/EntityMesh.ts +707 -0
  113. package/src/three/entity/animations.js +171 -0
  114. package/src/three/entity/armorModels.json +204 -0
  115. package/src/three/entity/armorModels.ts +36 -0
  116. package/src/three/entity/entities.json +6230 -0
  117. package/src/three/entity/exportedModels.js +38 -0
  118. package/src/three/entity/externalTextures.json +1 -0
  119. package/src/three/entity/models/allay.obj +325 -0
  120. package/src/three/entity/models/arrow.obj +60 -0
  121. package/src/three/entity/models/axolotl.obj +509 -0
  122. package/src/three/entity/models/blaze.obj +601 -0
  123. package/src/three/entity/models/boat.obj +417 -0
  124. package/src/three/entity/models/camel.obj +1061 -0
  125. package/src/three/entity/models/cat.obj +509 -0
  126. package/src/three/entity/models/chicken.obj +371 -0
  127. package/src/three/entity/models/cod.obj +371 -0
  128. package/src/three/entity/models/creeper.obj +279 -0
  129. package/src/three/entity/models/dolphin.obj +371 -0
  130. package/src/three/entity/models/ender_dragon.obj +2993 -0
  131. package/src/three/entity/models/enderman.obj +325 -0
  132. package/src/three/entity/models/endermite.obj +187 -0
  133. package/src/three/entity/models/fox.obj +463 -0
  134. package/src/three/entity/models/frog.obj +739 -0
  135. package/src/three/entity/models/ghast.obj +463 -0
  136. package/src/three/entity/models/goat.obj +601 -0
  137. package/src/three/entity/models/guardian.obj +1015 -0
  138. package/src/three/entity/models/horse.obj +1061 -0
  139. package/src/three/entity/models/llama.obj +509 -0
  140. package/src/three/entity/models/minecart.obj +233 -0
  141. package/src/three/entity/models/parrot.obj +509 -0
  142. package/src/three/entity/models/piglin.obj +739 -0
  143. package/src/three/entity/models/pillager.obj +371 -0
  144. package/src/three/entity/models/rabbit.obj +555 -0
  145. package/src/three/entity/models/sheep.obj +555 -0
  146. package/src/three/entity/models/shulker.obj +141 -0
  147. package/src/three/entity/models/sniffer.obj +693 -0
  148. package/src/three/entity/models/spider.obj +509 -0
  149. package/src/three/entity/models/tadpole.obj +95 -0
  150. package/src/three/entity/models/turtle.obj +371 -0
  151. package/src/three/entity/models/vex.obj +325 -0
  152. package/src/three/entity/models/villager.obj +509 -0
  153. package/src/three/entity/models/warden.obj +463 -0
  154. package/src/three/entity/models/witch.obj +647 -0
  155. package/src/three/entity/models/wolf.obj +509 -0
  156. package/src/three/entity/models/zombie_villager.obj +463 -0
  157. package/src/three/entity/objModels.js +1 -0
  158. package/src/three/fireworks.ts +661 -0
  159. package/src/three/fireworksRenderer.ts +434 -0
  160. package/src/three/globals.d.ts +7 -0
  161. package/src/three/graphicsBackend.ts +274 -0
  162. package/src/three/graphicsBackendOffThread.ts +107 -0
  163. package/src/three/hand.ts +89 -0
  164. package/src/three/holdingBlock.ts +926 -0
  165. package/src/three/index.ts +20 -0
  166. package/src/three/itemMesh.ts +427 -0
  167. package/src/three/modules.d.ts +14 -0
  168. package/src/three/panorama.ts +308 -0
  169. package/src/three/panoramaShared.ts +1 -0
  170. package/src/three/renderSlot.ts +82 -0
  171. package/src/three/skyboxRenderer.ts +406 -0
  172. package/src/three/starField.ts +13 -0
  173. package/src/three/threeJsMedia.ts +731 -0
  174. package/src/three/threeJsMethods.ts +15 -0
  175. package/src/three/threeJsParticles.ts +160 -0
  176. package/src/three/threeJsSound.ts +95 -0
  177. package/src/three/threeJsUtils.ts +90 -0
  178. package/src/three/waypointSprite.ts +435 -0
  179. package/src/three/waypoints.ts +163 -0
  180. package/src/three/world/cursorBlock.ts +172 -0
  181. package/src/three/world/vr.ts +257 -0
  182. package/src/three/worldGeometryExport.ts +259 -0
  183. package/src/three/worldGeometryHandler.ts +279 -0
  184. package/src/three/worldRendererThree.ts +1381 -0
  185. package/src/worldView/index.ts +6 -0
  186. package/src/worldView/types.ts +66 -0
  187. package/src/worldView/worldView.ts +424 -0
@@ -0,0 +1,1381 @@
1
+ import * as THREE from 'three'
2
+ import { Vec3 } from 'vec3'
3
+ import nbt from 'prismarine-nbt'
4
+ import PrismarineChatLoader from 'prismarine-chat'
5
+ import * as tweenJs from '@tweenjs/tween.js'
6
+ import { Biome } from 'minecraft-data'
7
+ import { renderSign } from '../sign-renderer'
8
+ import { DisplayWorldOptions, GraphicsInitOptions } from '@/graphicsBackend/types'
9
+ import { chunkPos, sectionPos } from '../lib/simpleUtils'
10
+ import { WorldRendererCommon } from '../lib/worldrendererCommon'
11
+ import { addNewStat } from '../lib/ui/newStats'
12
+ import { MesherGeometryOutput } from '../mesher/shared'
13
+ import { ItemSpecificContextProperties } from '@/playerState/types'
14
+ import { setBlockPosition } from '../mesher/standaloneRenderer'
15
+ import { getBannerTexture, createBannerMesh, releaseBannerTexture } from './bannerRenderer'
16
+ import { getMyHand } from './hand'
17
+ import HoldingBlock from './holdingBlock'
18
+ import { getMesh } from './entity/EntityMesh'
19
+ import { armorModel } from './entity/armorModels'
20
+ import { disposeObject, loadThreeJsTextureFromBitmap } from './threeJsUtils'
21
+ import { CursorBlock } from './world/cursorBlock'
22
+ import { getItemUv } from './appShared'
23
+ import { Entities } from './entities'
24
+ import { ThreeJsSound } from './threeJsSound'
25
+ import { CameraShake } from './cameraShake'
26
+ import { ThreeJsMedia } from './threeJsMedia'
27
+ import { Fountain } from './threeJsParticles'
28
+ import { WaypointsRenderer } from './waypoints'
29
+ import { FireworksRenderer } from './fireworksRenderer'
30
+ import { CinimaticScriptRunner, CinimaticScript } from './cinimaticScript'
31
+ import { DEFAULT_TEMPERATURE, SkyboxRenderer } from './skyboxRenderer'
32
+ import { FireworksManager } from './fireworks'
33
+ import { downloadWorldGeometry } from './worldGeometryExport'
34
+
35
+ type SectionKey = string
36
+
37
+ export class WorldRendererThree extends WorldRendererCommon {
38
+ outputFormat = 'threeJs' as const
39
+ sectionObjects: Record<string, THREE.Object3D & { foutain?: boolean }> = {}
40
+ chunkTextures = new Map<string, { [pos: string]: THREE.Texture }>()
41
+ signsCache = new Map<string, any>()
42
+ starField: StarField
43
+ cameraSectionPos: Vec3 = new Vec3(0, 0, 0)
44
+ holdingBlock: HoldingBlock
45
+ holdingBlockLeft: HoldingBlock
46
+ scene = new THREE.Scene()
47
+ ambientLight = new THREE.AmbientLight(0xcc_cc_cc)
48
+ directionalLight = new THREE.DirectionalLight(0xff_ff_ff, 0.5)
49
+ entities = new Entities(this, (globalThis as any).mcData)
50
+ cameraGroupVr?: THREE.Object3D
51
+ material = new THREE.MeshLambertMaterial({ vertexColors: true, transparent: true, alphaTest: 0.1 })
52
+ itemsTexture!: THREE.Texture
53
+ cursorBlock: CursorBlock
54
+ onRender: Array<() => void> = []
55
+ cameraShake: CameraShake
56
+ cameraContainer!: THREE.Object3D
57
+ media: ThreeJsMedia
58
+ waitingChunksToDisplay = {} as { [chunkKey: string]: SectionKey[] }
59
+ waypoints: WaypointsRenderer
60
+ cinimaticScript: CinimaticScriptRunner
61
+ camera!: THREE.PerspectiveCamera
62
+ renderTimeAvg = 0
63
+ // Memory usage tracking (in bytes)
64
+ estimatedMemoryUsage = 0
65
+ sectionsOffsetsAnimations = {} as {
66
+ [chunkKey: string]: {
67
+ time: number,
68
+ // also specifies direction
69
+ speedX: number,
70
+ speedY: number,
71
+ speedZ: number,
72
+
73
+ currentOffsetX: number,
74
+ currentOffsetY: number,
75
+ currentOffsetZ: number,
76
+
77
+ limitX?: number,
78
+ limitY?: number,
79
+ limitZ?: number,
80
+ }
81
+ }
82
+ fountains: Fountain[] = []
83
+ fireworksLegacy: FireworksRenderer
84
+ DEBUG_RAYCAST = false
85
+ skyboxRenderer: SkyboxRenderer
86
+ fireworks: FireworksManager
87
+
88
+ private currentPosTween?: tweenJs.Tween<THREE.Vector3>
89
+ private currentRotTween?: tweenJs.Tween<{ pitch: number, yaw: number }>
90
+
91
+ get tilesRendered() {
92
+ return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).tilesCount, 0)
93
+ }
94
+
95
+ get blocksRendered() {
96
+ return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).blocksCount, 0)
97
+ }
98
+
99
+ constructor(public renderer: THREE.WebGLRenderer, public initOptions: GraphicsInitOptions, public displayOptions: DisplayWorldOptions) {
100
+ if (!initOptions.resourcesManager) throw new Error('resourcesManager is required')
101
+ super(initOptions.resourcesManager, displayOptions, initOptions)
102
+
103
+ this.renderer = renderer
104
+ displayOptions.rendererState.renderer = WorldRendererThree.getRendererInfo(renderer) ?? '...'
105
+ this.starField = new StarField(this)
106
+ this.cursorBlock = new CursorBlock(this)
107
+ this.holdingBlock = new HoldingBlock(this)
108
+ this.holdingBlockLeft = new HoldingBlock(this, true)
109
+
110
+ // Initialize skybox renderer
111
+ this.skyboxRenderer = new SkyboxRenderer(this.scene, false, null)
112
+ void this.skyboxRenderer.init()
113
+
114
+ this.addDebugOverlay()
115
+ this.resetScene()
116
+ void this.init()
117
+
118
+ this.soundSystem = new ThreeJsSound(this)
119
+ this.cameraShake = new CameraShake(this, this.onRender)
120
+ this.media = new ThreeJsMedia(this)
121
+ this.fireworksLegacy = new FireworksRenderer(this)
122
+ this.waypoints = new WaypointsRenderer(this)
123
+ this.cinimaticScript = new CinimaticScriptRunner(
124
+ this,
125
+ (pos, yaw, pitch) => this.setCinimaticCamera(pos, yaw, pitch),
126
+ (fov) => this.setCinimaticFov(fov),
127
+ () => ({
128
+ position: new Vec3(this.cameraObject.position.x, this.cameraObject.position.y, this.cameraObject.position.z),
129
+ yaw: this.cameraShake.getBaseRotation().yaw,
130
+ pitch: this.cameraShake.getBaseRotation().pitch,
131
+ fov: this.camera.fov
132
+ })
133
+ )
134
+ this.fireworks = new FireworksManager(this.scene)
135
+
136
+ // this.fountain = new Fountain(this.scene, this.scene, {
137
+ // position: new THREE.Vector3(0, 10, 0),
138
+ // })
139
+
140
+ this.renderUpdateEmitter.on('chunkFinished', (chunkKey: string) => {
141
+ this.finishChunk(chunkKey)
142
+ })
143
+ this.worldSwitchActions()
144
+ }
145
+
146
+ get cameraObject() {
147
+ return this.cameraGroupVr ?? this.cameraContainer
148
+ }
149
+
150
+ worldSwitchActions() {
151
+ this.onWorldSwitched.push(() => {
152
+ // clear custom blocks
153
+ this.protocolCustomBlocks.clear()
154
+ // Reset section animations
155
+ this.sectionsOffsetsAnimations = {}
156
+ // Clear waypoints
157
+ this.waypoints.clear()
158
+ // Stop any running cinematic scripts
159
+ this.cinimaticScript.stopScript()
160
+ // Clear fireworks
161
+ this.fireworks.clear()
162
+ })
163
+ }
164
+
165
+ downloadWorldGeometry() {
166
+ downloadWorldGeometry(this, this.cameraObject.position, this.cameraShake.getBaseRotation(), 'world-geometry.json')
167
+ }
168
+
169
+ updateEntity(e, isPosUpdate = false) {
170
+ const overrides = {
171
+ rotation: {
172
+ head: {
173
+ x: e.headPitch ?? e.pitch,
174
+ y: e.headYaw,
175
+ z: 0
176
+ }
177
+ }
178
+ }
179
+ if (isPosUpdate) {
180
+ this.entities.updateEntityPosition(e, false, overrides)
181
+ } else {
182
+ this.entities.update(e, overrides)
183
+ }
184
+ }
185
+
186
+ updatePlayerEntity(e: any) {
187
+ this.entities.handlePlayerEntity(e)
188
+ }
189
+
190
+ resetScene() {
191
+ this.scene.matrixAutoUpdate = false // for perf
192
+ this.scene.background = new THREE.Color(this.initOptions.config.sceneBackground)
193
+ this.scene.add(this.ambientLight)
194
+ this.directionalLight.position.set(1, 1, 0.5).normalize()
195
+ this.directionalLight.castShadow = true
196
+ this.scene.add(this.directionalLight)
197
+
198
+ const size = this.renderer.getSize(new THREE.Vector2())
199
+ this.camera = new THREE.PerspectiveCamera(75, size.x / size.y, 0.1, 1000)
200
+ this.cameraContainer = new THREE.Object3D()
201
+ this.cameraContainer.add(this.camera)
202
+ this.scene.add(this.cameraContainer)
203
+ }
204
+
205
+ override watchReactivePlayerState() {
206
+ super.watchReactivePlayerState()
207
+ this.onReactivePlayerStateUpdated('inWater', (value) => {
208
+ this.skyboxRenderer.updateWaterState(value, this.playerStateReactive.waterBreathing)
209
+ })
210
+ this.onReactivePlayerStateUpdated('waterBreathing', (value) => {
211
+ this.skyboxRenderer.updateWaterState(this.playerStateReactive.inWater, value)
212
+ })
213
+ this.onReactivePlayerStateUpdated('ambientLight', (value) => {
214
+ if (!value) return
215
+ this.ambientLight.intensity = value
216
+ })
217
+ this.onReactivePlayerStateUpdated('directionalLight', (value) => {
218
+ if (!value) return
219
+ this.directionalLight.intensity = value
220
+ })
221
+ this.onReactivePlayerStateUpdated('lookingAtBlock', (value) => {
222
+ this.cursorBlock.setHighlightCursorBlock(value ? new Vec3(value.x, value.y, value.z) : null, value?.shapes)
223
+ })
224
+ this.onReactivePlayerStateUpdated('diggingBlock', (value) => {
225
+ this.cursorBlock.updateBreakAnimation(value ? { x: value.x, y: value.y, z: value.z } : undefined, value?.stage ?? null, value?.mergedShape)
226
+ })
227
+ this.onReactivePlayerStateUpdated('perspective', (value) => {
228
+ // Update camera perspective when it changes
229
+ const vecPos = new Vec3(this.cameraObject.position.x, this.cameraObject.position.y, this.cameraObject.position.z)
230
+ this.updateCamera(vecPos, this.cameraShake.getBaseRotation().yaw, this.cameraShake.getBaseRotation().pitch)
231
+ // todo also update camera when block within camera was changed
232
+ })
233
+ }
234
+
235
+ override watchReactiveConfig() {
236
+ super.watchReactiveConfig()
237
+ this.onReactiveConfigUpdated('showChunkBorders', (value) => {
238
+ this.updateShowChunksBorder(value)
239
+ })
240
+ this.onReactiveConfigUpdated('defaultSkybox', (value) => {
241
+ this.skyboxRenderer.updateDefaultSkybox(value)
242
+ })
243
+ }
244
+
245
+ changeHandSwingingState(isAnimationPlaying: boolean, isLeft = false) {
246
+ const holdingBlock = isLeft ? this.holdingBlockLeft : this.holdingBlock
247
+ if (isAnimationPlaying) {
248
+ holdingBlock.startSwing()
249
+ } else {
250
+ holdingBlock.stopSwing()
251
+ }
252
+ }
253
+
254
+ async updateAssetsData(): Promise<void> {
255
+ const resources = this.resourcesManager.currentResources
256
+
257
+ const oldTexture = this.material.map
258
+ const oldItemsTexture = this.itemsTexture
259
+
260
+ const texture = loadThreeJsTextureFromBitmap(resources.blocksAtlasImage!)
261
+ texture.needsUpdate = true
262
+ texture.flipY = false
263
+ this.material.map = texture
264
+
265
+ const itemsTexture = loadThreeJsTextureFromBitmap(resources.itemsAtlasImage!)
266
+ itemsTexture.needsUpdate = true
267
+ itemsTexture.flipY = false
268
+ this.itemsTexture = itemsTexture
269
+
270
+ if (oldTexture) {
271
+ oldTexture.dispose()
272
+ }
273
+ if (oldItemsTexture) {
274
+ oldItemsTexture.dispose()
275
+ }
276
+
277
+ await super.updateAssetsData()
278
+ this.onAllTexturesLoaded()
279
+ if (Object.keys(this.loadedChunks).length > 0) {
280
+ console.log('rerendering chunks because of texture update')
281
+ this.rerenderAllChunks()
282
+ }
283
+ }
284
+
285
+ onAllTexturesLoaded() {
286
+ this.holdingBlock.ready = true
287
+ this.holdingBlock.updateItem()
288
+ this.holdingBlockLeft.ready = true
289
+ this.holdingBlockLeft.updateItem()
290
+ }
291
+
292
+ changeBackgroundColor(color: [number, number, number]): void {
293
+ this.scene.background = new THREE.Color(color[0], color[1], color[2])
294
+ }
295
+
296
+ timeUpdated(newTime: number): void {
297
+ const nightTime = 13_500
298
+ const morningStart = 23_000
299
+ const displayStars = newTime > nightTime && newTime < morningStart
300
+ if (displayStars) {
301
+ this.starField.addToScene()
302
+ } else {
303
+ this.starField.remove()
304
+ }
305
+
306
+ this.skyboxRenderer.updateTime(newTime)
307
+ }
308
+
309
+ biomeUpdated(biome: Biome): void {
310
+ if (biome?.temperature !== undefined) {
311
+ this.skyboxRenderer.updateTemperature(biome.temperature)
312
+ }
313
+ }
314
+
315
+ biomeReset(): void {
316
+ // Reset to default temperature when biome is unknown
317
+ this.skyboxRenderer.updateTemperature(DEFAULT_TEMPERATURE)
318
+ }
319
+
320
+ getItemRenderData(item: Record<string, any>, specificProps: ItemSpecificContextProperties) {
321
+ return getItemUv(item, specificProps, this.resourcesManager, this.playerStateReactive)
322
+ }
323
+
324
+ async demoModel() {
325
+ //@ts-expect-error
326
+ const pos = cursorBlockRel(0, 1, 0).position
327
+
328
+ const mesh = (await getMyHand())!
329
+ // mesh.rotation.y = THREE.MathUtils.degToRad(90)
330
+ setBlockPosition(mesh, pos)
331
+ const helper = new THREE.BoxHelper(mesh, 0xff_ff_00)
332
+ mesh.add(helper)
333
+ this.scene.add(mesh)
334
+ }
335
+
336
+ demoItem() {
337
+ //@ts-expect-error
338
+ const pos = cursorBlockRel(0, 1, 0).position
339
+ const { mesh } = this.entities.getItemMesh({
340
+ itemId: 541,
341
+ }, {})!
342
+ mesh.position.set(pos.x + 0.5, pos.y + 0.5, pos.z + 0.5)
343
+ // mesh.scale.set(0.5, 0.5, 0.5)
344
+ const helper = new THREE.BoxHelper(mesh, 0xff_ff_00)
345
+ mesh.add(helper)
346
+ this.scene.add(mesh)
347
+ }
348
+
349
+ debugOverlayAdded = false
350
+ addDebugOverlay() {
351
+ if (this.debugOverlayAdded) return
352
+ this.debugOverlayAdded = true
353
+ const pane = addNewStat('debug-overlay')
354
+ setInterval(() => {
355
+ pane.setVisibility(this.displayAdvancedStats)
356
+ if (this.displayAdvancedStats) {
357
+ const formatBigNumber = (num: number) => {
358
+ return new Intl.NumberFormat('en-US', {}).format(num)
359
+ }
360
+ let text = ''
361
+ text += `C: ${formatBigNumber(this.renderer.info.render.calls)} `
362
+ text += `TR: ${formatBigNumber(this.renderer.info.render.triangles)} `
363
+ text += `TE: ${formatBigNumber(this.renderer.info.memory.textures)} `
364
+ text += `F: ${formatBigNumber(this.tilesRendered)} `
365
+ text += `B: ${formatBigNumber(this.blocksRendered)} `
366
+ text += `MEM: ${this.getEstimatedMemoryUsage().readable}`
367
+ pane.updateText(text)
368
+ this.backendInfoReport = text
369
+ }
370
+ }, 200)
371
+ }
372
+
373
+ /**
374
+ * Optionally update data that are depedendent on the viewer position
375
+ */
376
+ updatePosDataChunk(key: string) {
377
+ const [x, y, z] = key.split(',').map(x => Math.floor(+x / 16))
378
+ // sum of distances: x + y + z
379
+ const chunkDistance = Math.abs(x - this.cameraSectionPos.x) + Math.abs(y - this.cameraSectionPos.y) + Math.abs(z - this.cameraSectionPos.z)
380
+ const section = this.sectionObjects[key].children.find(child => child.name === 'mesh')!
381
+ section.renderOrder = 500 - chunkDistance
382
+ }
383
+
384
+ override updateViewerPosition(pos: Vec3): void {
385
+ this.viewerChunkPosition = pos
386
+ }
387
+
388
+ cameraSectionPositionUpdate() {
389
+ // eslint-disable-next-line guard-for-in
390
+ for (const key in this.sectionObjects) {
391
+ const value = this.sectionObjects[key]
392
+ if (!value) continue
393
+ this.updatePosDataChunk(key)
394
+ }
395
+ }
396
+
397
+ getDir(current: number, origin: number) {
398
+ if (current === origin) return 0
399
+ return current < origin ? 1 : -1
400
+ }
401
+
402
+ finishChunk(chunkKey: string) {
403
+ for (const sectionKey of this.waitingChunksToDisplay[chunkKey] ?? []) {
404
+ this.sectionObjects[sectionKey].visible = true
405
+ }
406
+ delete this.waitingChunksToDisplay[chunkKey]
407
+ }
408
+
409
+ // debugRecomputedDeletedObjects = 0
410
+ handleWorkerMessage(data: { geometry: MesherGeometryOutput, key, type }): void {
411
+ if (data.type !== 'geometry') return
412
+ let object: THREE.Object3D = this.sectionObjects[data.key]
413
+ if (object) {
414
+ // Track memory usage removal for existing section
415
+ this.removeSectionMemoryUsage(object)
416
+ // Cleanup banner textures before disposing
417
+ object.traverse((child) => {
418
+ if ((child as any).bannerTexture) {
419
+ releaseBannerTexture((child as any).bannerTexture)
420
+ }
421
+ })
422
+ this.scene.remove(object)
423
+ disposeObject(object)
424
+ delete this.sectionObjects[data.key]
425
+ }
426
+
427
+ const chunkCoords = data.key.split(',')
428
+ if (!this.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] || !data.geometry.positions.length || !this.active) return
429
+
430
+ // if (object) {
431
+ // this.debugRecomputedDeletedObjects++
432
+ // }
433
+
434
+ const geometry = new THREE.BufferGeometry()
435
+ const positionAttr = new THREE.BufferAttribute(data.geometry.positions, 3)
436
+ const normalAttr = new THREE.BufferAttribute(data.geometry.normals, 3)
437
+ const colorAttr = new THREE.BufferAttribute(data.geometry.colors, 3)
438
+ const uvAttr = new THREE.BufferAttribute(data.geometry.uvs, 2)
439
+ const indexAttr = new THREE.BufferAttribute(data.geometry.indices as Uint32Array | Uint16Array, 1)
440
+
441
+ geometry.setAttribute('position', positionAttr)
442
+ geometry.setAttribute('normal', normalAttr)
443
+ geometry.setAttribute('color', colorAttr)
444
+ geometry.setAttribute('uv', uvAttr)
445
+ geometry.index = indexAttr
446
+
447
+ // Track memory usage for this section (before disposing CPU arrays)
448
+ this.addSectionMemoryUsage(geometry)
449
+
450
+ // Force GPU upload and then dispose CPU arrays to free RAM
451
+ this.disposeCpuArraysAfterGpuUpload(geometry)
452
+
453
+ const mesh = new THREE.Mesh(geometry, this.material)
454
+ mesh.position.set(data.geometry.sx, data.geometry.sy, data.geometry.sz)
455
+ mesh.name = 'mesh'
456
+ object = new THREE.Group()
457
+ object.add(mesh)
458
+ // mesh with static dimensions: 16x16x16
459
+ const staticChunkMesh = new THREE.Mesh(new THREE.BoxGeometry(16, 16, 16), new THREE.MeshBasicMaterial({ color: 0x00_00_00, transparent: true, opacity: 0 }))
460
+ staticChunkMesh.position.set(data.geometry.sx, data.geometry.sy, data.geometry.sz)
461
+ const boxHelper = new THREE.BoxHelper(staticChunkMesh, 0xff_ff_00)
462
+ boxHelper.name = 'helper'
463
+ object.add(boxHelper)
464
+ object.name = 'chunk';
465
+ (object as any).tilesCount = data.geometry.positions.length / 3 / 4;
466
+ (object as any).blocksCount = data.geometry.blocksCount
467
+ if (!this.displayOptions.inWorldRenderingConfig.showChunkBorders) {
468
+ boxHelper.visible = false
469
+ }
470
+ // should not compute it once
471
+ if (Object.keys(data.geometry.signs).length) {
472
+ for (const [posKey, { isWall, isHanging, rotation }] of Object.entries(data.geometry.signs)) {
473
+ const signBlockEntity = this.blockEntities[posKey]
474
+ if (!signBlockEntity) continue
475
+ const [x, y, z] = posKey.split(',')
476
+ const sign = this.renderSign(new Vec3(+x, +y, +z), rotation, isWall, isHanging, nbt.simplify(signBlockEntity))
477
+ if (!sign) continue
478
+ object.add(sign)
479
+ }
480
+ }
481
+ if (Object.keys(data.geometry.heads).length) {
482
+ for (const [posKey, { isWall, rotation }] of Object.entries(data.geometry.heads)) {
483
+ const headBlockEntity = this.blockEntities[posKey]
484
+ if (!headBlockEntity) continue
485
+ const [x, y, z] = posKey.split(',')
486
+ const head = this.renderHead(new Vec3(+x, +y, +z), rotation, isWall, nbt.simplify(headBlockEntity))
487
+ if (!head) continue
488
+ object.add(head)
489
+ }
490
+ }
491
+ if (Object.keys(data.geometry.banners).length) {
492
+ for (const [posKey, { isWall, rotation, blockName }] of Object.entries(data.geometry.banners)) {
493
+ const bannerBlockEntity = this.blockEntities[posKey]
494
+ if (!bannerBlockEntity) continue
495
+ const [x, y, z] = posKey.split(',')
496
+ const bannerTexture = getBannerTexture(this, blockName, nbt.simplify(bannerBlockEntity))
497
+ if (!bannerTexture) continue
498
+ const banner = createBannerMesh(new Vec3(+x, +y, +z), rotation, isWall, bannerTexture)
499
+ object.add(banner)
500
+ }
501
+ }
502
+ this.sectionObjects[data.key] = object
503
+ if (this.displayOptions.inWorldRenderingConfig._renderByChunks) {
504
+ object.visible = false
505
+ const chunkKey = `${chunkCoords[0]},${chunkCoords[2]}`
506
+ this.waitingChunksToDisplay[chunkKey] ??= []
507
+ this.waitingChunksToDisplay[chunkKey].push(data.key)
508
+ if (this.finishedChunks[chunkKey]) {
509
+ // todo it might happen even when it was not an update
510
+ this.finishChunk(chunkKey)
511
+ }
512
+ }
513
+
514
+ this.updatePosDataChunk(data.key)
515
+ object.matrixAutoUpdate = false
516
+ mesh.onAfterRender = (renderer, scene, camera, geometry, material, group) => {
517
+ // mesh.matrixAutoUpdate = false
518
+ }
519
+
520
+ this.scene.add(object)
521
+ }
522
+
523
+ /**
524
+ * Dispose CPU arrays after GPU upload to reduce RAM usage
525
+ */
526
+ private disposeCpuArraysAfterGpuUpload(geometry: THREE.BufferGeometry): void {
527
+ // Set up callbacks to dispose CPU arrays after GPU upload
528
+ // Three.js will automatically upload to GPU on first render
529
+ const { attributes } = geometry
530
+ for (const attributeName of Object.keys(attributes)) {
531
+ const attribute = attributes[attributeName]
532
+ if (attribute instanceof THREE.InterleavedBufferAttribute) continue
533
+ if (attribute.onUploadCallback) {
534
+ // If there's already a callback, chain it
535
+ const existingCallback = attribute.onUploadCallback
536
+ attribute.onUploadCallback = () => {
537
+ existingCallback()
538
+ this.disposeCpuArray(attribute)
539
+ }
540
+ } else {
541
+ attribute.onUploadCallback = () => this.disposeCpuArray(attribute)
542
+ }
543
+ // Force the upload callback by marking as needing update
544
+ attribute.needsUpdate = true
545
+ }
546
+
547
+ // Handle index attribute
548
+ if (geometry.index) {
549
+ if (geometry.index.onUploadCallback) {
550
+ const existingCallback = geometry.index.onUploadCallback
551
+ geometry.index.onUploadCallback = () => {
552
+ existingCallback()
553
+ this.disposeCpuArray(geometry.index!)
554
+ }
555
+ } else {
556
+ geometry.index.onUploadCallback = () => this.disposeCpuArray(geometry.index!)
557
+ }
558
+ geometry.index.needsUpdate = true
559
+ }
560
+ }
561
+
562
+ /**
563
+ * Dispose CPU array data from buffer attribute
564
+ */
565
+ private disposeCpuArray(attribute: THREE.BufferAttribute): void {
566
+ // Clear the CPU array reference to free memory
567
+ // Note: This makes the attribute read-only from CPU side
568
+ if (attribute.array) {
569
+ // Store original array type for potential recreation
570
+ // attribute.userData = attribute.userData || {}
571
+ // attribute.userData.originalArrayType = attribute.array.constructor.name
572
+ // attribute.userData.originalLength = attribute.array.length
573
+ // attribute.userData.originalItemSize = attribute.itemSize
574
+
575
+ // Clear the array reference
576
+ ; (attribute as any).array = null
577
+ }
578
+ }
579
+
580
+ getSignTexture(position: Vec3, blockEntity, isHanging, backSide = false) {
581
+ const chunk = chunkPos(position)
582
+ let textures = this.chunkTextures.get(`${chunk[0]},${chunk[1]}`)
583
+ if (!textures) {
584
+ textures = {}
585
+ this.chunkTextures.set(`${chunk[0]},${chunk[1]}`, textures)
586
+ }
587
+ const texturekey = `${position.x},${position.y},${position.z}`
588
+ // todo investigate bug and remove this so don't need to clean in section dirty
589
+ if (textures[texturekey]) return textures[texturekey]
590
+
591
+ const PrismarineChat = PrismarineChatLoader(this.version)
592
+ const canvas = renderSign(blockEntity, isHanging, PrismarineChat)
593
+ if (!canvas) return
594
+ const tex = new THREE.Texture(canvas)
595
+ tex.magFilter = THREE.NearestFilter
596
+ tex.minFilter = THREE.NearestFilter
597
+ tex.needsUpdate = true
598
+ textures[texturekey] = tex
599
+ return tex
600
+ }
601
+
602
+ getCameraPosition() {
603
+ const worldPos = new THREE.Vector3()
604
+ this.camera.getWorldPosition(worldPos)
605
+ return worldPos
606
+ }
607
+
608
+ getSectionCameraPosition() {
609
+ const pos = this.getCameraPosition()
610
+ return new Vec3(
611
+ Math.floor(pos.x / 16),
612
+ Math.floor(pos.y / 16),
613
+ Math.floor(pos.z / 16)
614
+ )
615
+ }
616
+
617
+ updateCameraSectionPos() {
618
+ const newSectionPos = this.getSectionCameraPosition()
619
+ if (!this.cameraSectionPos.equals(newSectionPos)) {
620
+ this.cameraSectionPos = newSectionPos
621
+ this.cameraSectionPositionUpdate()
622
+ }
623
+ }
624
+
625
+ setFirstPersonCamera(pos: Vec3 | null, yaw: number, pitch: number) {
626
+ const yOffset = this.playerStateReactive.eyeHeight
627
+
628
+ this.updateCamera(pos?.offset(0, yOffset, 0) ?? null, yaw, pitch)
629
+ this.media.tryIntersectMedia()
630
+ this.updateCameraSectionPos()
631
+ }
632
+
633
+ getThirdPersonCamera(pos: THREE.Vector3 | null, yaw: number, pitch: number) {
634
+ pos ??= this.cameraObject.position
635
+
636
+ // Calculate camera offset based on perspective
637
+ const isBack = this.playerStateReactive.perspective === 'third_person_back'
638
+ const distance = 4 // Default third person distance
639
+
640
+ // Calculate direction vector using proper world orientation
641
+ // We need to get the camera's current look direction and use that for positioning
642
+
643
+ // Create a direction vector that represents where the camera is looking
644
+ // This matches the Three.js camera coordinate system
645
+ const direction = new THREE.Vector3(0, 0, -1) // Forward direction in camera space
646
+
647
+ // Apply the same rotation that's applied to the camera container
648
+ const pitchQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), pitch)
649
+ const yawQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), yaw)
650
+ const finalQuat = new THREE.Quaternion().multiplyQuaternions(yawQuat, pitchQuat)
651
+
652
+ // Transform the direction vector by the camera's rotation
653
+ direction.applyQuaternion(finalQuat)
654
+
655
+ // For back view, we want the camera behind the player (opposite to view direction)
656
+ // For front view, we want the camera in front of the player (same as view direction)
657
+ if (isBack) {
658
+ direction.multiplyScalar(-1)
659
+ }
660
+
661
+ // Create debug visualization if advanced stats are enabled
662
+ if (this.DEBUG_RAYCAST) {
663
+ this.debugRaycast(pos, direction, distance)
664
+ }
665
+
666
+ // Perform raycast to avoid camera going through blocks
667
+ const raycaster = new THREE.Raycaster()
668
+ raycaster.set(pos, direction)
669
+ raycaster.far = distance // Limit raycast distance
670
+
671
+ // Filter to only nearby chunks for performance
672
+ const nearbyChunks = Object.values(this.sectionObjects)
673
+ .filter(obj => obj.name === 'chunk' && obj.visible)
674
+ .filter(obj => {
675
+ // Get the mesh child which has the actual geometry
676
+ const mesh = obj.children.find(child => child.name === 'mesh')
677
+ if (!mesh) return false
678
+
679
+ // Check distance from player position to chunk
680
+ const chunkWorldPos = new THREE.Vector3()
681
+ mesh.getWorldPosition(chunkWorldPos)
682
+ const distance = pos.distanceTo(chunkWorldPos)
683
+ return distance < 80 // Only check chunks within 80 blocks
684
+ })
685
+
686
+ // Get all mesh children for raycasting
687
+ const meshes: THREE.Object3D[] = []
688
+ for (const chunk of nearbyChunks) {
689
+ const mesh = chunk.children.find(child => child.name === 'mesh')
690
+ if (mesh) meshes.push(mesh)
691
+ }
692
+
693
+ const intersects = raycaster.intersectObjects(meshes, false)
694
+
695
+ let finalDistance = distance
696
+ if (intersects.length > 0) {
697
+ // Use intersection distance minus a small offset to prevent clipping
698
+ finalDistance = Math.max(0.5, intersects[0].distance - 0.2)
699
+ }
700
+
701
+ const finalPos = new Vec3(
702
+ pos.x + direction.x * finalDistance,
703
+ pos.y + direction.y * finalDistance,
704
+ pos.z + direction.z * finalDistance
705
+ )
706
+
707
+ return finalPos
708
+ }
709
+
710
+ private debugRaycastHelper?: THREE.ArrowHelper
711
+ private debugHitPoint?: THREE.Mesh
712
+
713
+ private debugRaycast(pos: THREE.Vector3, direction: THREE.Vector3, distance: number) {
714
+ // Remove existing debug objects
715
+ if (this.debugRaycastHelper) {
716
+ this.scene.remove(this.debugRaycastHelper)
717
+ this.debugRaycastHelper = undefined
718
+ }
719
+ if (this.debugHitPoint) {
720
+ this.scene.remove(this.debugHitPoint)
721
+ this.debugHitPoint = undefined
722
+ }
723
+
724
+ // Create raycast arrow
725
+ this.debugRaycastHelper = new THREE.ArrowHelper(
726
+ direction.clone().normalize(),
727
+ pos,
728
+ distance,
729
+ 0xff_00_00, // Red color
730
+ distance * 0.1,
731
+ distance * 0.05
732
+ )
733
+ this.scene.add(this.debugRaycastHelper)
734
+
735
+ // Create hit point indicator
736
+ const hitGeometry = new THREE.SphereGeometry(0.2, 8, 8)
737
+ const hitMaterial = new THREE.MeshBasicMaterial({ color: 0x00_ff_00 })
738
+ this.debugHitPoint = new THREE.Mesh(hitGeometry, hitMaterial)
739
+ this.debugHitPoint.position.copy(pos).add(direction.clone().multiplyScalar(distance))
740
+ this.scene.add(this.debugHitPoint)
741
+ }
742
+
743
+ prevFramePerspective = null as string | null
744
+
745
+ setCinimaticCamera(pos: Vec3, yaw: number, pitch: number): void {
746
+ // Directly set camera position and rotation for cinematic mode
747
+ this.cameraObject.position.set(pos.x, pos.y, pos.z)
748
+ this.cameraShake.setBaseRotation(pitch, yaw)
749
+ this.updateCameraSectionPos()
750
+ }
751
+
752
+ setCinimaticFov(fov: number): void {
753
+ this.camera.fov = fov
754
+ this.camera.updateProjectionMatrix()
755
+ }
756
+
757
+ updateCamera(pos: Vec3 | null, yaw: number, pitch: number): void {
758
+ // Skip position/rotation updates if cinematic script is running
759
+ if (this.cinimaticScript.running) {
760
+ return
761
+ }
762
+
763
+ // if (this.freeFlyMode) {
764
+ // pos = this.freeFlyState.position
765
+ // pitch = this.freeFlyState.pitch
766
+ // yaw = this.freeFlyState.yaw
767
+ // }
768
+
769
+ if (pos) {
770
+ if (this.renderer.xr.isPresenting) {
771
+ pos.y -= this.camera.position.y // Fix Y position of camera in world
772
+ }
773
+
774
+ this.currentPosTween?.stop()
775
+ // Use instant camera updates (0 delay) in playground mode when camera controls are enabled
776
+ const tweenDelay = this.displayOptions.inWorldRenderingConfig.instantCameraUpdate
777
+ ? 0
778
+ : (this.playerStateUtils.isSpectatingEntity() ? 150 : 50)
779
+ this.currentPosTween = new tweenJs.Tween(this.cameraObject.position).to({ x: pos.x, y: pos.y, z: pos.z }, tweenDelay).start()
780
+ // this.freeFlyState.position = pos
781
+ }
782
+
783
+ if (this.playerStateUtils.isSpectatingEntity()) {
784
+ const rotation = this.cameraShake.getBaseRotation()
785
+ // wrap in the correct direction
786
+ let yawOffset = 0
787
+ const halfPi = Math.PI / 2
788
+ if (rotation.yaw < halfPi && yaw > Math.PI + halfPi) {
789
+ yawOffset = -Math.PI * 2
790
+ } else if (yaw < halfPi && rotation.yaw > Math.PI + halfPi) {
791
+ yawOffset = Math.PI * 2
792
+ }
793
+ this.currentRotTween?.stop()
794
+ this.currentRotTween = new tweenJs.Tween(rotation).to({ pitch, yaw: yaw + yawOffset }, 100)
795
+ .onUpdate(params => this.cameraShake.setBaseRotation(params.pitch, params.yaw - yawOffset)).start()
796
+ } else {
797
+ this.currentRotTween?.stop()
798
+ this.cameraShake.setBaseRotation(pitch, yaw)
799
+
800
+ const { perspective } = this.playerStateReactive
801
+ if (perspective === 'third_person_back' || perspective === 'third_person_front') {
802
+ // Use getThirdPersonCamera for proper raycasting with max distance of 4
803
+ const currentCameraPos = this.cameraObject.position
804
+ const thirdPersonPos = this.getThirdPersonCamera(
805
+ new THREE.Vector3(currentCameraPos.x, currentCameraPos.y, currentCameraPos.z),
806
+ yaw,
807
+ pitch
808
+ )
809
+
810
+ const distance = currentCameraPos.distanceTo(new THREE.Vector3(thirdPersonPos.x, thirdPersonPos.y, thirdPersonPos.z))
811
+ // Apply Z offset based on perspective and calculated distance
812
+ const zOffset = perspective === 'third_person_back' ? distance : -distance
813
+ this.camera.position.set(0, 0, zOffset)
814
+
815
+ if (perspective === 'third_person_front') {
816
+ // Flip camera view 180 degrees around Y axis for front view
817
+ this.camera.rotation.set(0, Math.PI, 0)
818
+ } else {
819
+ this.camera.rotation.set(0, 0, 0)
820
+ }
821
+ } else {
822
+ this.camera.position.set(0, 0, 0)
823
+ this.camera.rotation.set(0, 0, 0)
824
+
825
+ // remove any debug raycasting
826
+ if (this.debugRaycastHelper) {
827
+ this.scene.remove(this.debugRaycastHelper)
828
+ this.debugRaycastHelper = undefined
829
+ }
830
+ if (this.debugHitPoint) {
831
+ this.scene.remove(this.debugHitPoint)
832
+ this.debugHitPoint = undefined
833
+ }
834
+ }
835
+ }
836
+
837
+ this.updateCameraSectionPos()
838
+ }
839
+
840
+ debugChunksVisibilityOverride() {
841
+ const { chunksRenderAboveOverride, chunksRenderBelowOverride, chunksRenderDistanceOverride, chunksRenderAboveEnabled, chunksRenderBelowEnabled, chunksRenderDistanceEnabled } = this.reactiveDebugParams
842
+
843
+ const baseY = this.cameraSectionPos.y * 16
844
+
845
+ if (
846
+ this.displayOptions.inWorldRenderingConfig.enableDebugOverlay &&
847
+ chunksRenderAboveOverride !== undefined ||
848
+ chunksRenderBelowOverride !== undefined ||
849
+ chunksRenderDistanceOverride !== undefined
850
+ ) {
851
+ for (const [key, object] of Object.entries(this.sectionObjects)) {
852
+ const [x, y, z] = key.split(',').map(Number)
853
+ const isVisible =
854
+ // eslint-disable-next-line no-constant-binary-expression, sonarjs/no-redundant-boolean
855
+ (chunksRenderAboveEnabled && chunksRenderAboveOverride !== undefined) ? y <= (baseY + chunksRenderAboveOverride) : true &&
856
+ // eslint-disable-next-line @stylistic/indent-binary-ops, no-constant-binary-expression, sonarjs/no-redundant-boolean
857
+ (chunksRenderBelowEnabled && chunksRenderBelowOverride !== undefined) ? y >= (baseY - chunksRenderBelowOverride) : true &&
858
+ // eslint-disable-next-line @stylistic/indent-binary-ops
859
+ (chunksRenderDistanceEnabled && chunksRenderDistanceOverride !== undefined) ? Math.abs(y - baseY) <= chunksRenderDistanceOverride : true
860
+
861
+ object.visible = isVisible
862
+ }
863
+ } else {
864
+ for (const object of Object.values(this.sectionObjects)) {
865
+ object.visible = true
866
+ }
867
+ }
868
+ }
869
+
870
+ render(sizeChanged = false) {
871
+ this.currentRenderedFrames++
872
+ if (this.reactiveDebugParams.stopRendering) return
873
+ this.debugChunksVisibilityOverride()
874
+ const start = performance.now()
875
+ this.lastRendered = performance.now()
876
+ this.cursorBlock.render()
877
+ this.updateSectionOffsets()
878
+
879
+ // Update skybox position to follow camera
880
+ const cameraPos = this.getCameraPosition()
881
+ this.skyboxRenderer.update(cameraPos, this.viewDistance)
882
+
883
+ const sizeOrFovChanged = sizeChanged || this.displayOptions.inWorldRenderingConfig.fov !== this.camera.fov
884
+ if (sizeOrFovChanged) {
885
+ const size = this.renderer.getSize(new THREE.Vector2())
886
+ this.camera.aspect = size.width / size.height
887
+ this.camera.fov = this.displayOptions.inWorldRenderingConfig.fov
888
+ this.camera.updateProjectionMatrix()
889
+ }
890
+
891
+ if (!this.reactiveDebugParams.disableEntities) {
892
+ this.entities.render()
893
+ }
894
+
895
+ // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
896
+ const cam = this.cameraGroupVr instanceof THREE.Group ? this.cameraGroupVr.children.find(child => child instanceof THREE.PerspectiveCamera) as THREE.PerspectiveCamera : this.camera
897
+ this.renderer.render(this.scene, cam)
898
+
899
+ if (
900
+ this.displayOptions.inWorldRenderingConfig.showHand &&
901
+ this.playerStateReactive.gameMode !== 'spectator' &&
902
+ this.playerStateReactive.perspective === 'first_person' &&
903
+ // !this.freeFlyMode &&
904
+ !this.renderer.xr.isPresenting
905
+ ) {
906
+ this.holdingBlock.render(this.camera, this.renderer, this.ambientLight, this.directionalLight)
907
+ this.holdingBlockLeft.render(this.camera, this.renderer, this.ambientLight, this.directionalLight)
908
+ }
909
+
910
+ for (const fountain of this.fountains) {
911
+ if (this.sectionObjects[fountain.sectionId] && !this.sectionObjects[fountain.sectionId].foutain) {
912
+ fountain.createParticles(this.sectionObjects[fountain.sectionId])
913
+ this.sectionObjects[fountain.sectionId].foutain = true
914
+ }
915
+ fountain.render()
916
+ }
917
+
918
+ this.waypoints.render()
919
+ this.fireworks.update()
920
+
921
+ for (const onRender of this.onRender) {
922
+ onRender()
923
+ }
924
+ const end = performance.now()
925
+ const totalTime = end - start
926
+ this.renderTimeAvgCount++
927
+ this.renderTimeAvg = ((this.renderTimeAvg * (this.renderTimeAvgCount - 1)) + totalTime) / this.renderTimeAvgCount
928
+ this.renderTimeMax = Math.max(this.renderTimeMax, totalTime)
929
+ }
930
+
931
+ renderHead(position: Vec3, rotation: number, isWall: boolean, blockEntity) {
932
+ let textureData: string
933
+ if (blockEntity.SkullOwner) {
934
+ textureData = blockEntity.SkullOwner.Properties?.textures?.[0]?.Value
935
+ } else {
936
+ textureData = blockEntity.profile?.properties?.find(p => p.name === 'textures')?.value
937
+ }
938
+ if (!textureData) return
939
+
940
+ try {
941
+ const decodedData = JSON.parse(Buffer.from(textureData, 'base64').toString())
942
+ let skinUrl = decodedData.textures?.SKIN?.url
943
+ const { skinTexturesProxy } = this.worldRendererConfig
944
+ if (skinTexturesProxy) {
945
+ skinUrl = skinUrl?.replace('http://textures.minecraft.net/', skinTexturesProxy)
946
+ .replace('https://textures.minecraft.net/', skinTexturesProxy)
947
+ }
948
+
949
+ const mesh = getMesh(this, skinUrl, armorModel.head as any)
950
+ const group = new THREE.Group()
951
+ if (isWall) {
952
+ mesh.position.set(0, 0.3125, 0.3125)
953
+ }
954
+ // move head model down as armor have a different offset than blocks
955
+ mesh.position.y -= 23 / 16
956
+ group.add(mesh)
957
+ group.position.set(position.x + 0.5, position.y + 0.045, position.z + 0.5)
958
+ group.rotation.set(
959
+ 0,
960
+ -THREE.MathUtils.degToRad(rotation * (isWall ? 90 : 45 / 2)),
961
+ 0
962
+ )
963
+ group.scale.set(0.8, 0.8, 0.8)
964
+ return group
965
+ } catch (err) {
966
+ console.error('Error decoding player texture:', err)
967
+ }
968
+ }
969
+
970
+ renderSign(position: Vec3, rotation: number, isWall: boolean, isHanging: boolean, blockEntity) {
971
+ const tex = this.getSignTexture(position, blockEntity, isHanging)
972
+
973
+ if (!tex) return
974
+
975
+ // todo implement
976
+ // const key = JSON.stringify({ position, rotation, isWall })
977
+ // if (this.signsCache.has(key)) {
978
+ // console.log('cached', key)
979
+ // } else {
980
+ // this.signsCache.set(key, tex)
981
+ // }
982
+
983
+ const mesh = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), new THREE.MeshBasicMaterial({ map: tex, transparent: true }))
984
+ mesh.renderOrder = 999
985
+
986
+ const lineHeight = 7 / 16
987
+ const scaleFactor = isHanging ? 1.3 : 1
988
+ mesh.scale.set(1 * scaleFactor, lineHeight * scaleFactor, 1 * scaleFactor)
989
+
990
+ const thickness = (isHanging ? 2 : 1.5) / 16
991
+ const wallSpacing = 0.25 / 16
992
+ if (isWall && !isHanging) {
993
+ mesh.position.set(0, 0, -0.5 + thickness + wallSpacing + 0.0001)
994
+ } else {
995
+ mesh.position.set(0, 0, thickness / 2 + 0.0001)
996
+ }
997
+
998
+ const group = new THREE.Group()
999
+ group.rotation.set(
1000
+ 0,
1001
+ -THREE.MathUtils.degToRad(rotation * (isWall ? 90 : 45 / 2)),
1002
+ 0
1003
+ )
1004
+ group.add(mesh)
1005
+ const height = (isHanging ? 10 : 8) / 16
1006
+ const heightOffset = (isHanging ? 0 : isWall ? 4.333 : 9.333) / 16
1007
+ const textPosition = height / 2 + heightOffset
1008
+ group.position.set(position.x + 0.5, position.y + textPosition, position.z + 0.5)
1009
+ return group
1010
+ }
1011
+
1012
+ lightUpdate(chunkX: number, chunkZ: number) {
1013
+ // set all sections in the chunk dirty
1014
+ for (let y = this.worldSizeParams.minY; y < this.worldSizeParams.worldHeight; y += 16) {
1015
+ this.setSectionDirty(new Vec3(chunkX, y, chunkZ))
1016
+ }
1017
+ }
1018
+
1019
+ rerenderAllChunks() { // todo not clear what to do with loading chunks
1020
+ for (const key of Object.keys(this.sectionObjects)) {
1021
+ const [x, y, z] = key.split(',').map(Number)
1022
+ this.setSectionDirty(new Vec3(x, y, z))
1023
+ }
1024
+ }
1025
+
1026
+ updateShowChunksBorder(value: boolean) {
1027
+ for (const object of Object.values(this.sectionObjects)) {
1028
+ for (const child of object.children) {
1029
+ if (child.name === 'helper') {
1030
+ child.visible = value
1031
+ }
1032
+ }
1033
+ }
1034
+ }
1035
+
1036
+ resetWorld() {
1037
+ super.resetWorld()
1038
+
1039
+ for (const mesh of Object.values(this.sectionObjects)) {
1040
+ // Track memory usage removal for all sections
1041
+ this.removeSectionMemoryUsage(mesh)
1042
+ this.scene.remove(mesh)
1043
+ }
1044
+
1045
+ // Reset memory tracking since all sections are cleared
1046
+ this.estimatedMemoryUsage = 0
1047
+
1048
+ // Clean up debug objects
1049
+ if (this.debugRaycastHelper) {
1050
+ this.scene.remove(this.debugRaycastHelper)
1051
+ this.debugRaycastHelper = undefined
1052
+ }
1053
+ if (this.debugHitPoint) {
1054
+ this.scene.remove(this.debugHitPoint)
1055
+ this.debugHitPoint = undefined
1056
+ }
1057
+ }
1058
+
1059
+ getLoadedChunksRelative(pos: Vec3, includeY = false) {
1060
+ const [currentX, currentY, currentZ] = sectionPos(pos)
1061
+ return Object.fromEntries(Object.entries(this.sectionObjects).map(([key, o]) => {
1062
+ const [xRaw, yRaw, zRaw] = key.split(',').map(Number)
1063
+ const [x, y, z] = sectionPos({ x: xRaw, y: yRaw, z: zRaw })
1064
+ const setKey = includeY ? `${x - currentX},${y - currentY},${z - currentZ}` : `${x - currentX},${z - currentZ}`
1065
+ return [setKey, o]
1066
+ }))
1067
+ }
1068
+
1069
+ cleanChunkTextures(x, z) {
1070
+ const textures = this.chunkTextures.get(`${Math.floor(x / 16)},${Math.floor(z / 16)}`) ?? {}
1071
+ for (const key of Object.keys(textures)) {
1072
+ textures[key].dispose()
1073
+ delete textures[key]
1074
+ }
1075
+ }
1076
+
1077
+ readdChunks() {
1078
+ for (const key of Object.keys(this.sectionObjects)) {
1079
+ this.scene.remove(this.sectionObjects[key])
1080
+ }
1081
+ setTimeout(() => {
1082
+ for (const key of Object.keys(this.sectionObjects)) {
1083
+ this.scene.add(this.sectionObjects[key])
1084
+ }
1085
+ }, 500)
1086
+ }
1087
+
1088
+ disableUpdates(children = this.scene.children) {
1089
+ for (const child of children) {
1090
+ child.matrixWorldNeedsUpdate = false
1091
+ this.disableUpdates(child.children ?? [])
1092
+ }
1093
+ }
1094
+
1095
+ removeColumn(x, z) {
1096
+ super.removeColumn(x, z)
1097
+
1098
+ this.cleanChunkTextures(x, z)
1099
+ for (let y = this.worldSizeParams.minY; y < this.worldSizeParams.worldHeight; y += 16) {
1100
+ this.setSectionDirty(new Vec3(x, y, z), false)
1101
+ const key = `${x},${y},${z}`
1102
+ const mesh = this.sectionObjects[key]
1103
+ if (mesh) {
1104
+ // Track memory usage removal
1105
+ this.removeSectionMemoryUsage(mesh)
1106
+ // Cleanup banner textures before disposing
1107
+ mesh.traverse((child) => {
1108
+ if ((child as any).bannerTexture) {
1109
+ releaseBannerTexture((child as any).bannerTexture)
1110
+ }
1111
+ })
1112
+ this.scene.remove(mesh)
1113
+ disposeObject(mesh)
1114
+ }
1115
+ delete this.sectionObjects[key]
1116
+ }
1117
+ }
1118
+
1119
+ setSectionDirty(...args: Parameters<WorldRendererCommon['setSectionDirty']>) {
1120
+ const [pos] = args
1121
+ this.cleanChunkTextures(pos.x, pos.z) // todo don't do this!
1122
+ super.setSectionDirty(...args)
1123
+ }
1124
+
1125
+ static getRendererInfo(renderer: THREE.WebGLRenderer) {
1126
+ try {
1127
+ const gl = renderer.getContext()
1128
+ return `${gl.getParameter(gl.getExtension('WEBGL_debug_renderer_info')!.UNMASKED_RENDERER_WEBGL)}`
1129
+ } catch (err) {
1130
+ console.warn('Failed to get renderer info', err)
1131
+ }
1132
+ }
1133
+
1134
+ worldStop() {
1135
+ this.media.onWorldStop()
1136
+ }
1137
+
1138
+ destroy(): void {
1139
+ this.fireworksLegacy.destroy()
1140
+ super.destroy()
1141
+ this.skyboxRenderer.dispose()
1142
+ this.fireworks.dispose()
1143
+ }
1144
+
1145
+ shouldObjectVisible(object: THREE.Object3D) {
1146
+ // Get chunk coordinates
1147
+ const chunkX = Math.floor(object.position.x / 16) * 16
1148
+ const chunkZ = Math.floor(object.position.z / 16) * 16
1149
+ const sectionY = Math.floor(object.position.y / 16) * 16
1150
+
1151
+ const chunkKey = `${chunkX},${chunkZ}`
1152
+ const sectionKey = `${chunkX},${sectionY},${chunkZ}`
1153
+
1154
+ return !!this.finishedChunks[chunkKey] || !!this.sectionObjects[sectionKey]
1155
+ }
1156
+
1157
+ handleUserClick(button: 'left' | 'right') {
1158
+ this.media.handleUserClick(button)
1159
+ }
1160
+
1161
+ updateSectionOffsets() {
1162
+ const currentTime = performance.now()
1163
+ for (const [key, anim] of Object.entries(this.sectionsOffsetsAnimations)) {
1164
+ const timeDelta = (currentTime - anim.time) / 1000 // Convert to seconds
1165
+ anim.time = currentTime
1166
+
1167
+ // Update offsets based on speed and time delta
1168
+ anim.currentOffsetX += anim.speedX * timeDelta
1169
+ anim.currentOffsetY += anim.speedY * timeDelta
1170
+ anim.currentOffsetZ += anim.speedZ * timeDelta
1171
+
1172
+ // Apply limits if they exist
1173
+ if (anim.limitX !== undefined) {
1174
+ if (anim.speedX > 0) {
1175
+ anim.currentOffsetX = Math.min(anim.currentOffsetX, anim.limitX)
1176
+ } else {
1177
+ anim.currentOffsetX = Math.max(anim.currentOffsetX, anim.limitX)
1178
+ }
1179
+ }
1180
+ if (anim.limitY !== undefined) {
1181
+ if (anim.speedY > 0) {
1182
+ anim.currentOffsetY = Math.min(anim.currentOffsetY, anim.limitY)
1183
+ } else {
1184
+ anim.currentOffsetY = Math.max(anim.currentOffsetY, anim.limitY)
1185
+ }
1186
+ }
1187
+ if (anim.limitZ !== undefined) {
1188
+ if (anim.speedZ > 0) {
1189
+ anim.currentOffsetZ = Math.min(anim.currentOffsetZ, anim.limitZ)
1190
+ } else {
1191
+ anim.currentOffsetZ = Math.max(anim.currentOffsetZ, anim.limitZ)
1192
+ }
1193
+ }
1194
+
1195
+ // Apply the offset to the section object
1196
+ const section = this.sectionObjects[key]
1197
+ if (section) {
1198
+ section.position.set(
1199
+ anim.currentOffsetX,
1200
+ anim.currentOffsetY,
1201
+ anim.currentOffsetZ
1202
+ )
1203
+ section.updateMatrix()
1204
+ }
1205
+ }
1206
+ }
1207
+
1208
+ reloadWorld() {
1209
+ this.entities.reloadEntities()
1210
+ }
1211
+
1212
+ /**
1213
+ * Estimate memory usage of BufferGeometry attributes
1214
+ */
1215
+ private estimateGeometryMemoryUsage(geometry: THREE.BufferGeometry): number {
1216
+ let memoryBytes = 0
1217
+
1218
+ // Calculate memory for each attribute
1219
+ const { attributes } = geometry
1220
+ for (const [name, attribute] of Object.entries(attributes)) {
1221
+ if (attribute?.array) {
1222
+ // Each number in typed arrays takes different bytes:
1223
+ // Float32Array: 4 bytes per number
1224
+ // Uint32Array: 4 bytes per number
1225
+ // Uint16Array: 2 bytes per number
1226
+ const bytesPerElement = attribute.array.BYTES_PER_ELEMENT
1227
+ memoryBytes += attribute.array.length * bytesPerElement
1228
+ }
1229
+ }
1230
+
1231
+ // Calculate memory for indices
1232
+ if (geometry.index?.array) {
1233
+ const bytesPerElement = geometry.index.array.BYTES_PER_ELEMENT
1234
+ memoryBytes += geometry.index.array.length * bytesPerElement
1235
+ }
1236
+
1237
+ return memoryBytes
1238
+ }
1239
+
1240
+ /**
1241
+ * Update memory usage when section is added
1242
+ */
1243
+ private addSectionMemoryUsage(geometry: THREE.BufferGeometry): void {
1244
+ const memoryUsage = this.estimateGeometryMemoryUsage(geometry)
1245
+ this.estimatedMemoryUsage += memoryUsage
1246
+ }
1247
+
1248
+ /**
1249
+ * Update memory usage when section is removed
1250
+ */
1251
+ private removeSectionMemoryUsage(object: THREE.Object3D): void {
1252
+ // Find mesh with geometry in the object
1253
+ const mesh = object.children.find(child => child.name === 'mesh') as THREE.Mesh
1254
+ if (mesh?.geometry) {
1255
+ const memoryUsage = this.estimateGeometryMemoryUsage(mesh.geometry)
1256
+ this.estimatedMemoryUsage -= memoryUsage
1257
+ this.estimatedMemoryUsage = Math.max(0, this.estimatedMemoryUsage) // Ensure non-negative
1258
+ }
1259
+ }
1260
+
1261
+ /**
1262
+ * Get estimated memory usage in a human-readable format
1263
+ */
1264
+ getEstimatedMemoryUsage(): { bytes: number; readable: string } {
1265
+ const bytes = this.estimatedMemoryUsage
1266
+ const mb = bytes / (1024 * 1024)
1267
+ const readable = `${mb.toFixed(2)} MB`
1268
+ return { bytes, readable }
1269
+ }
1270
+ }
1271
+
1272
+ class StarField {
1273
+ points?: THREE.Points
1274
+ private _enabled = true
1275
+ get enabled() {
1276
+ return this._enabled
1277
+ }
1278
+
1279
+ set enabled(value) {
1280
+ this._enabled = value
1281
+ if (this.points) {
1282
+ this.points.visible = value
1283
+ }
1284
+ }
1285
+
1286
+ constructor(
1287
+ private readonly worldRenderer: WorldRendererThree
1288
+ ) {
1289
+ const clock = new THREE.Clock()
1290
+ const speed = 0.2
1291
+ this.worldRenderer.onRender.push(() => {
1292
+ if (!this.points) return
1293
+ this.points.position.copy(this.worldRenderer.getCameraPosition());
1294
+ (this.points.material as StarfieldMaterial).uniforms.time.value = clock.getElapsedTime() * speed
1295
+ })
1296
+ }
1297
+
1298
+ addToScene() {
1299
+ if (this.points || !this.enabled) return
1300
+
1301
+ const radius = 80
1302
+ const depth = 50
1303
+ const count = 7000
1304
+ const factor = 7
1305
+ const saturation = 10
1306
+
1307
+ const geometry = new THREE.BufferGeometry()
1308
+
1309
+ const genStar = r => new THREE.Vector3().setFromSpherical(new THREE.Spherical(r, Math.acos(1 - Math.random() * 2), Math.random() * 2 * Math.PI))
1310
+
1311
+ const positions = [] as number[]
1312
+ const colors = [] as number[]
1313
+ const sizes = Array.from({ length: count }, () => (0.5 + 0.5 * Math.random()) * factor)
1314
+ const color = new THREE.Color()
1315
+ let r = radius + depth
1316
+ const increment = depth / count
1317
+ for (let i = 0; i < count; i++) {
1318
+ r -= increment * Math.random()
1319
+ positions.push(...genStar(r).toArray())
1320
+ color.setHSL(i / count, saturation, 0.9)
1321
+ colors.push(color.r, color.g, color.b)
1322
+ }
1323
+
1324
+ geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3))
1325
+ geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3))
1326
+ geometry.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1))
1327
+
1328
+ // Create a material
1329
+ const material = new StarfieldMaterial()
1330
+ material.blending = THREE.AdditiveBlending
1331
+ material.depthTest = false
1332
+ material.transparent = true
1333
+
1334
+ // Create points and add them to the scene
1335
+ this.points = new THREE.Points(geometry, material)
1336
+ this.worldRenderer.scene.add(this.points)
1337
+
1338
+ this.points.renderOrder = -1
1339
+ }
1340
+
1341
+ remove() {
1342
+ if (this.points) {
1343
+ this.points.geometry.dispose();
1344
+ (this.points.material as THREE.Material).dispose()
1345
+ this.worldRenderer.scene.remove(this.points)
1346
+
1347
+ this.points = undefined
1348
+ }
1349
+ }
1350
+ }
1351
+
1352
+ const version = parseInt(THREE.REVISION.replaceAll(/\D+/g, ''), 10)
1353
+ class StarfieldMaterial extends THREE.ShaderMaterial {
1354
+ constructor() {
1355
+ super({
1356
+ uniforms: { time: { value: 0 }, fade: { value: 1 } },
1357
+ vertexShader: /* glsl */ `
1358
+ uniform float time;
1359
+ attribute float size;
1360
+ varying vec3 vColor;
1361
+ attribute vec3 color;
1362
+ void main() {
1363
+ vColor = color;
1364
+ vec4 mvPosition = modelViewMatrix * vec4(position, 0.5);
1365
+ gl_PointSize = 0.7 * size * (30.0 / -mvPosition.z) * (3.0 + sin(time + 100.0));
1366
+ gl_Position = projectionMatrix * mvPosition;
1367
+ }`,
1368
+ fragmentShader: /* glsl */ `
1369
+ uniform sampler2D pointTexture;
1370
+ uniform float fade;
1371
+ varying vec3 vColor;
1372
+ void main() {
1373
+ float opacity = 1.0;
1374
+ gl_FragColor = vec4(vColor, 1.0);
1375
+
1376
+ #include <tonemapping_fragment>
1377
+ #include <${version >= 154 ? 'colorspace_fragment' : 'encodings_fragment'}>
1378
+ }`,
1379
+ })
1380
+ }
1381
+ }