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,926 @@
1
+ import * as THREE from 'three'
2
+ import * as tweenJs from '@tweenjs/tween.js'
3
+ import PrismarineItem from 'prismarine-item'
4
+ import worldBlockProvider, { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider'
5
+ import { BlockModel } from 'mc-assets'
6
+ import { DebugGui } from '../lib/DebugGui'
7
+ import { SmoothSwitcher } from '../lib/smoothSwitcher'
8
+ import { watchProperty } from '../lib/utils/proxy'
9
+ import { WorldRendererConfig } from '../lib/worldrendererCommon'
10
+ import { getMyHand } from './hand'
11
+ import { WorldRendererThree } from './worldRendererThree'
12
+ import { disposeObject } from './threeJsUtils'
13
+ import { HandItemBlock, MovementState } from '@/playerState/types'
14
+ import { PlayerStateRenderer } from '@/playerState/playerState'
15
+ import { getThreeBlockModelGroup } from '@/mesher/standaloneRenderer'
16
+
17
+ const rotationPositionData = {
18
+ itemRight: {
19
+ 'rotation': [
20
+ 0,
21
+ -90,
22
+ 25
23
+ ],
24
+ 'translation': [
25
+ 1.13,
26
+ 3.2,
27
+ 1.13
28
+ ],
29
+ 'scale': [
30
+ 0.68,
31
+ 0.68,
32
+ 0.68
33
+ ]
34
+ },
35
+ itemLeft: {
36
+ 'rotation': [
37
+ 0,
38
+ 90,
39
+ -25
40
+ ],
41
+ 'translation': [
42
+ 1.13,
43
+ 3.2,
44
+ 1.13
45
+ ],
46
+ 'scale': [
47
+ 0.68,
48
+ 0.68,
49
+ 0.68
50
+ ]
51
+ },
52
+ blockRight: {
53
+ 'rotation': [
54
+ 0,
55
+ 45,
56
+ 0
57
+ ],
58
+ 'translation': [
59
+ 0,
60
+ 0,
61
+ 0
62
+ ],
63
+ 'scale': [
64
+ 0.4,
65
+ 0.4,
66
+ 0.4
67
+ ]
68
+ },
69
+ blockLeft: {
70
+ 'rotation': [
71
+ 0,
72
+ 225,
73
+ 0
74
+ ],
75
+ 'translation': [
76
+ 0,
77
+ 0,
78
+ 0
79
+ ],
80
+ 'scale': [
81
+ 0.4,
82
+ 0.4,
83
+ 0.4
84
+ ]
85
+ }
86
+ }
87
+
88
+ export default class HoldingBlock {
89
+ // TODO refactor with the tree builder for better visual understanding
90
+ holdingBlock: THREE.Object3D | undefined = undefined
91
+ blockSwapAnimation: {
92
+ switcher: SmoothSwitcher
93
+ // hidden: boolean
94
+ } | undefined = undefined
95
+ cameraGroup = new THREE.Mesh()
96
+ objectOuterGroup = new THREE.Group() // 3
97
+ objectInnerGroup = new THREE.Group() // 4
98
+ holdingBlockInnerGroup = new THREE.Group() // 5
99
+ camera = new THREE.PerspectiveCamera(75, 1, 0.1, 100)
100
+ stopUpdate = false
101
+ lastHeldItem: HandItemBlock | undefined
102
+ isSwinging = false
103
+ nextIterStopCallbacks: Array<() => void> | undefined
104
+ idleAnimator: HandIdleAnimator | undefined
105
+ ready = false
106
+ lastUpdate = 0
107
+ playerHand: THREE.Object3D | undefined
108
+ offHandDisplay = false
109
+ offHandModeLegacy = false
110
+
111
+ swingAnimator: HandSwingAnimator | undefined
112
+ config: WorldRendererConfig
113
+
114
+ constructor(public worldRenderer: WorldRendererThree, public offHand = false) {
115
+ this.initCameraGroup()
116
+ this.worldRenderer.onReactivePlayerStateUpdated('heldItemMain', () => {
117
+ if (!this.offHand) {
118
+ this.updateItem()
119
+ }
120
+ }, false)
121
+ this.worldRenderer.onReactivePlayerStateUpdated('heldItemOff', () => {
122
+ if (this.offHand) {
123
+ this.updateItem()
124
+ }
125
+ }, false)
126
+ this.config = worldRenderer.displayOptions.inWorldRenderingConfig
127
+
128
+ this.offHandDisplay = this.offHand
129
+ // this.offHandDisplay = true
130
+ if (!this.offHand) {
131
+ // load default hand
132
+ void getMyHand().then((hand) => {
133
+ this.playerHand = hand
134
+ // trigger update
135
+ this.updateItem()
136
+ }).then(() => {
137
+ // now watch over the player skin
138
+ watchProperty(
139
+ async () => {
140
+ return getMyHand(this.worldRenderer.playerStateReactive.playerSkin, this.worldRenderer.playerStateReactive.onlineMode ? this.worldRenderer.playerStateReactive.username : undefined)
141
+ },
142
+ this.worldRenderer.playerStateReactive,
143
+ 'playerSkin',
144
+ (newHand) => {
145
+ if (newHand) {
146
+ this.playerHand = newHand
147
+ // trigger update
148
+ this.updateItem()
149
+ }
150
+ },
151
+ (oldHand) => {
152
+ disposeObject(oldHand!, true)
153
+ }
154
+ )
155
+ })
156
+ }
157
+ }
158
+
159
+ updateItem() {
160
+ if (!this.ready) return
161
+ const item = this.offHand ? this.worldRenderer.playerStateReactive.heldItemOff : this.worldRenderer.playerStateReactive.heldItemMain
162
+ if (item) {
163
+ void this.setNewItem(item)
164
+ } else if (this.offHand) {
165
+ void this.setNewItem()
166
+ } else {
167
+ void this.setNewItem({
168
+ type: 'hand',
169
+ })
170
+ }
171
+ }
172
+
173
+ initCameraGroup() {
174
+ this.cameraGroup = new THREE.Mesh()
175
+ }
176
+
177
+ startSwing() {
178
+ this.swingAnimator?.startSwing()
179
+ }
180
+
181
+ stopSwing() {
182
+ this.swingAnimator?.stopSwing()
183
+ }
184
+
185
+ render(originalCamera: THREE.PerspectiveCamera, renderer: THREE.WebGLRenderer, ambientLight: THREE.AmbientLight, directionalLight: THREE.DirectionalLight) {
186
+ if (!this.lastHeldItem) return
187
+ const now = performance.now()
188
+ if (this.lastUpdate && now - this.lastUpdate > 50) { // one tick
189
+ void this.replaceItemModel(this.lastHeldItem)
190
+ }
191
+
192
+ // Only update idle animation if not swinging
193
+ if (this.swingAnimator?.isCurrentlySwinging() || this.swingAnimator?.debugParams.animationStage) {
194
+ this.swingAnimator?.update()
195
+ } else {
196
+ this.idleAnimator?.update()
197
+ }
198
+
199
+ this.blockSwapAnimation?.switcher.update()
200
+
201
+ const scene = new THREE.Scene()
202
+ scene.add(this.cameraGroup)
203
+ // if (this.camera.aspect !== originalCamera.aspect) {
204
+ // this.camera.aspect = originalCamera.aspect
205
+ // this.camera.updateProjectionMatrix()
206
+ // }
207
+ this.updateCameraGroup()
208
+ scene.add(ambientLight.clone())
209
+ scene.add(directionalLight.clone())
210
+
211
+ const viewerSize = renderer.getSize(new THREE.Vector2())
212
+ const minSize = Math.min(viewerSize.width, viewerSize.height)
213
+ const x = viewerSize.width - minSize
214
+
215
+ // Mirror the scene for offhand by scaling
216
+ const { offHandDisplay } = this
217
+ if (offHandDisplay) {
218
+ this.cameraGroup.scale.x = -1
219
+ }
220
+
221
+ renderer.autoClear = false
222
+ renderer.clearDepth()
223
+ if (this.offHandDisplay) {
224
+ renderer.setViewport(0, 0, minSize, minSize)
225
+ } else {
226
+ const x = viewerSize.width - minSize
227
+ // if (x) x -= x / 4
228
+ renderer.setViewport(x, 0, minSize, minSize)
229
+ }
230
+ renderer.render(scene, this.camera)
231
+ renderer.setViewport(0, 0, viewerSize.width, viewerSize.height)
232
+
233
+ // Reset the mirroring after rendering
234
+ if (offHandDisplay) {
235
+ this.cameraGroup.scale.x = 1
236
+ }
237
+ }
238
+
239
+ // worldTest () {
240
+ // const mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshPhongMaterial({ color: 0x00_00_ff, transparent: true, opacity: 0.5 }))
241
+ // mesh.position.set(0.5, 0.5, 0.5)
242
+ // const group = new THREE.Group()
243
+ // group.add(mesh)
244
+ // group.position.set(-0.5, -0.5, -0.5)
245
+ // const outerGroup = new THREE.Group()
246
+ // outerGroup.add(group)
247
+ // outerGroup.position.set(this.camera.position.x, this.camera.position.y, this.camera.position.z)
248
+ // this.scene.add(outerGroup)
249
+
250
+ // new tweenJs.Tween(group.rotation).to({ z: THREE.MathUtils.degToRad(90) }, 1000).yoyo(true).repeat(Infinity).start()
251
+ // }
252
+
253
+ async playBlockSwapAnimation(forceState: 'appeared' | 'disappeared') {
254
+ this.blockSwapAnimation ??= {
255
+ switcher: new SmoothSwitcher(
256
+ () => ({
257
+ y: this.objectInnerGroup.position.y
258
+ }),
259
+ (property, value) => {
260
+ if (property === 'y') this.objectInnerGroup.position.y = value
261
+ },
262
+ {
263
+ y: 16 // units per second
264
+ }
265
+ )
266
+ }
267
+
268
+ const newState = forceState
269
+ // if (forceState && newState !== forceState) {
270
+ // throw new Error(`forceState does not match current state ${forceState} !== ${newState}`)
271
+ // }
272
+
273
+ const targetY = this.objectInnerGroup.position.y + (this.objectInnerGroup.scale.y * 1.5 * (newState === 'appeared' ? 1 : -1))
274
+
275
+ // if (newState === this.blockSwapAnimation.switcher.transitioningToStateName) {
276
+ // return false
277
+ // }
278
+
279
+ let cancelled = false
280
+ return new Promise<boolean>((resolve) => {
281
+ this.blockSwapAnimation!.switcher.transitionTo(
282
+ { y: targetY },
283
+ newState,
284
+ () => {
285
+ if (!cancelled) {
286
+ resolve(true)
287
+ }
288
+ },
289
+ () => {
290
+ cancelled = true
291
+ resolve(false)
292
+ }
293
+ )
294
+ })
295
+ }
296
+
297
+ isDifferentItem(block: HandItemBlock | undefined) {
298
+ const Item = PrismarineItem(this.worldRenderer.version)
299
+ if (!this.lastHeldItem) {
300
+ return true
301
+ }
302
+ if (this.lastHeldItem.name !== block?.name) {
303
+ return true
304
+ }
305
+ // eslint-disable-next-line sonarjs/prefer-single-boolean-return
306
+ if (!Item.equal(this.lastHeldItem.fullItem, block?.fullItem ?? {}) || JSON.stringify(this.lastHeldItem.fullItem.components) !== JSON.stringify(block?.fullItem?.components)) {
307
+ return true
308
+ }
309
+
310
+ return false
311
+ }
312
+
313
+ updateCameraGroup() {
314
+ if (this.stopUpdate) return
315
+ const { camera } = this
316
+ this.cameraGroup.position.copy(camera.position)
317
+ this.cameraGroup.rotation.copy(camera.rotation)
318
+
319
+ // const viewerSize = viewer.renderer.getSize(new THREE.Vector2())
320
+ // const aspect = viewerSize.width / viewerSize.height
321
+ const aspect = 1
322
+
323
+
324
+ // Adjust the position based on the aspect ratio
325
+ const { position, scale: scaleData } = this.getHandHeld3d()
326
+ const distance = -position.z
327
+ const side = this.offHandModeLegacy ? -1 : 1
328
+ this.objectOuterGroup.position.set(
329
+ distance * position.x * aspect * side,
330
+ distance * position.y,
331
+ -distance
332
+ )
333
+
334
+ // const scale = Math.min(0.8, Math.max(1, 1 * aspect))
335
+ const scale = scaleData * 2.22 * 0.2
336
+ this.objectOuterGroup.scale.set(scale, scale, scale)
337
+ }
338
+
339
+ lastItemModelName: string | undefined
340
+ private async createItemModel(handItem: HandItemBlock): Promise<{ model: THREE.Object3D; type: 'hand' | 'block' | 'item' } | undefined> {
341
+ this.lastUpdate = performance.now()
342
+ if (!handItem || (handItem.type === 'hand' && !this.playerHand)) return undefined
343
+
344
+ let blockInner: THREE.Object3D | undefined
345
+ if (handItem.type === 'item' || handItem.type === 'block') {
346
+ const result = this.worldRenderer.entities.getItemMesh({
347
+ ...handItem.fullItem,
348
+ itemId: handItem.id,
349
+ }, {
350
+ 'minecraft:display_context': 'firstperson',
351
+ 'minecraft:use_duration': this.worldRenderer.playerStateReactive.itemUsageTicks,
352
+ 'minecraft:using_item': !!this.worldRenderer.playerStateReactive.itemUsageTicks,
353
+ }, false, this.lastItemModelName)
354
+ if (result) {
355
+ const { mesh: itemMesh, isBlock, modelName } = result
356
+ if (isBlock) {
357
+ blockInner = itemMesh
358
+ handItem.type = 'block'
359
+ } else {
360
+ itemMesh.position.set(0.5, 0.5, 0.5)
361
+ blockInner = itemMesh
362
+ handItem.type = 'item'
363
+ }
364
+ this.lastItemModelName = modelName
365
+ }
366
+ } else {
367
+ blockInner = this.playerHand!
368
+ }
369
+ if (!blockInner) return
370
+ blockInner.name = 'holdingBlock'
371
+
372
+ const rotationDeg = this.getHandHeld3d().rotation
373
+ blockInner.rotation.x = THREE.MathUtils.degToRad(rotationDeg.x)
374
+ blockInner.rotation.y = THREE.MathUtils.degToRad(rotationDeg.y)
375
+ blockInner.rotation.z = THREE.MathUtils.degToRad(rotationDeg.z)
376
+
377
+ return { model: blockInner, type: handItem.type }
378
+ }
379
+
380
+ async replaceItemModel(handItem?: HandItemBlock): Promise<void> {
381
+ // if switch animation is in progress, do not replace the item
382
+ if (this.blockSwapAnimation?.switcher.isTransitioning) return
383
+
384
+ if (!handItem) {
385
+ this.holdingBlock?.removeFromParent()
386
+ this.holdingBlock = undefined
387
+ this.swingAnimator?.stopSwing()
388
+ this.swingAnimator = undefined
389
+ this.idleAnimator = undefined
390
+ return
391
+ }
392
+
393
+ const result = await this.createItemModel(handItem)
394
+ if (!result) return
395
+
396
+ // Update the model without changing the group structure
397
+ this.holdingBlock?.removeFromParent()
398
+ this.holdingBlock = result.model
399
+ this.holdingBlockInnerGroup.add(result.model)
400
+
401
+
402
+ }
403
+
404
+ testUnknownBlockSwitch() {
405
+ void this.setNewItem({
406
+ type: 'item',
407
+ name: 'minecraft:some-unknown-block',
408
+ id: 0,
409
+ fullItem: {}
410
+ })
411
+ }
412
+
413
+ switchRequest = 0
414
+ async setNewItem(handItem?: HandItemBlock) {
415
+ if (!this.isDifferentItem(handItem)) return
416
+ this.lastItemModelName = undefined
417
+ const switchRequest = ++this.switchRequest
418
+ this.lastHeldItem = handItem
419
+ let playAppearAnimation = false
420
+ if (this.holdingBlock) {
421
+ // play disappear animation
422
+ playAppearAnimation = true
423
+ const result = await this.playBlockSwapAnimation('disappeared')
424
+ if (!result) return
425
+ this.holdingBlock?.removeFromParent()
426
+ this.holdingBlock = undefined
427
+ }
428
+
429
+ if (!handItem) {
430
+ this.swingAnimator?.stopSwing()
431
+ this.swingAnimator = undefined
432
+ this.idleAnimator = undefined
433
+ this.blockSwapAnimation = undefined
434
+ return
435
+ }
436
+
437
+ if (switchRequest !== this.switchRequest) return
438
+ const result = await this.createItemModel(handItem)
439
+ if (!result || switchRequest !== this.switchRequest) return
440
+
441
+ const blockOuterGroup = new THREE.Group()
442
+ this.holdingBlockInnerGroup.removeFromParent()
443
+ this.holdingBlockInnerGroup = new THREE.Group()
444
+ this.holdingBlockInnerGroup.add(result.model)
445
+ blockOuterGroup.add(this.holdingBlockInnerGroup)
446
+ this.holdingBlock = result.model
447
+ this.objectInnerGroup = new THREE.Group()
448
+ this.objectInnerGroup.add(blockOuterGroup)
449
+ this.objectInnerGroup.position.set(-0.5, -0.5, -0.5)
450
+ if (playAppearAnimation) {
451
+ this.objectInnerGroup.position.y -= this.objectInnerGroup.scale.y * 1.5
452
+ }
453
+ Object.assign(blockOuterGroup.position, { x: 0.5, y: 0.5, z: 0.5 })
454
+
455
+ this.objectOuterGroup = new THREE.Group()
456
+ this.objectOuterGroup.add(this.objectInnerGroup)
457
+
458
+ this.cameraGroup.add(this.objectOuterGroup)
459
+ const rotationDeg = this.getHandHeld3d().rotation
460
+ this.objectOuterGroup.rotation.y = THREE.MathUtils.degToRad(rotationDeg.yOuter)
461
+
462
+ if (playAppearAnimation) {
463
+ await this.playBlockSwapAnimation('appeared')
464
+ }
465
+
466
+ this.swingAnimator = new HandSwingAnimator(this.holdingBlockInnerGroup)
467
+ this.swingAnimator.type = result.type
468
+ if (this.config.viewBobbing) {
469
+ this.idleAnimator = new HandIdleAnimator(this.holdingBlockInnerGroup, this.worldRenderer.playerStateReactive)
470
+ }
471
+ }
472
+
473
+ getHandHeld3d() {
474
+ const type = this.lastHeldItem?.type ?? 'hand'
475
+ const side = this.offHandModeLegacy ? 'Left' : 'Right'
476
+
477
+ let scale = 0.8 * 1.15 // default scale for hand
478
+ let position = {
479
+ x: 0.4,
480
+ y: -0.7,
481
+ z: -0.45
482
+ }
483
+ let rotation = {
484
+ x: -32.4,
485
+ y: 42.8,
486
+ z: -41.3,
487
+ yOuter: 0
488
+ }
489
+
490
+ if (type === 'item') {
491
+ const itemData = rotationPositionData[`item${side}`]
492
+ position = {
493
+ x: -0.05,
494
+ y: -0.7,
495
+ z: -0.45
496
+ }
497
+ rotation = {
498
+ x: itemData.rotation[0],
499
+ y: itemData.rotation[1],
500
+ z: itemData.rotation[2],
501
+ yOuter: 0
502
+ }
503
+ scale = itemData.scale[0] * 1.15
504
+ } else if (type === 'block') {
505
+ const blockData = rotationPositionData[`block${side}`]
506
+ position = {
507
+ x: 0.4,
508
+ y: -0.7,
509
+ z: -0.45
510
+ }
511
+ rotation = {
512
+ x: blockData.rotation[0],
513
+ y: blockData.rotation[1],
514
+ z: blockData.rotation[2],
515
+ yOuter: 0
516
+ }
517
+ scale = blockData.scale[0] * 1.15
518
+ }
519
+
520
+ return {
521
+ rotation,
522
+ position,
523
+ scale
524
+ }
525
+ }
526
+ }
527
+
528
+ class HandIdleAnimator {
529
+ globalTime = 0
530
+ lastTime = 0
531
+ currentState: MovementState
532
+ targetState: MovementState
533
+ defaultPosition: { x: number; y: number; z: number; rotationX: number; rotationY: number; rotationZ: number }
534
+ private readonly idleOffset = { y: 0, rotationZ: 0 }
535
+ private readonly tween = new tweenJs.Group()
536
+ private idleTween: tweenJs.Tween<{ y: number; rotationZ: number }> | null = null
537
+ private readonly stateSwitcher: SmoothSwitcher
538
+
539
+ // Debug parameters
540
+ private readonly debugParams = {
541
+ // Transition durations for different state changes
542
+ walkingSpeed: 8,
543
+ sprintingSpeed: 16,
544
+ walkingAmplitude: { x: 1 / 30, y: 1 / 10, rotationZ: 0.25 },
545
+ sprintingAmplitude: { x: 1 / 30, y: 1 / 10, rotationZ: 0.4 }
546
+ }
547
+
548
+ private readonly debugGui: DebugGui
549
+
550
+ constructor(public handMesh: THREE.Object3D, public playerState: PlayerStateRenderer) {
551
+ this.handMesh = handMesh
552
+ this.globalTime = 0
553
+ this.currentState = 'NOT_MOVING'
554
+ this.targetState = 'NOT_MOVING'
555
+
556
+ this.defaultPosition = {
557
+ x: handMesh.position.x,
558
+ y: handMesh.position.y,
559
+ z: handMesh.position.z,
560
+ rotationX: handMesh.rotation.x,
561
+ rotationY: handMesh.rotation.y,
562
+ rotationZ: handMesh.rotation.z
563
+ }
564
+
565
+ // Initialize state switcher with appropriate speeds
566
+ this.stateSwitcher = new SmoothSwitcher(
567
+ () => {
568
+ return {
569
+ x: this.handMesh.position.x,
570
+ y: this.handMesh.position.y,
571
+ z: this.handMesh.position.z,
572
+ rotationX: this.handMesh.rotation.x,
573
+ rotationY: this.handMesh.rotation.y,
574
+ rotationZ: this.handMesh.rotation.z
575
+ }
576
+ },
577
+ (property, value) => {
578
+ switch (property) {
579
+ case 'x': this.handMesh.position.x = value; break
580
+ case 'y': this.handMesh.position.y = value; break
581
+ case 'z': this.handMesh.position.z = value; break
582
+ case 'rotationX': this.handMesh.rotation.x = value; break
583
+ case 'rotationY': this.handMesh.rotation.y = value; break
584
+ case 'rotationZ': this.handMesh.rotation.z = value; break
585
+ }
586
+ },
587
+ {
588
+ x: 2, // units per second
589
+ y: 2,
590
+ z: 2,
591
+ rotation: Math.PI // radians per second
592
+ }
593
+ )
594
+
595
+ // Initialize debug GUI
596
+ this.debugGui = new DebugGui('idle_animator', this.debugParams)
597
+ // this.debugGui.activate()
598
+ }
599
+
600
+ private startIdleAnimation() {
601
+ if (this.idleTween) {
602
+ this.idleTween.stop()
603
+ }
604
+
605
+ // Start from current position for smooth transition
606
+ this.idleOffset.y = this.handMesh.position.y - this.defaultPosition.y
607
+ this.idleOffset.rotationZ = this.handMesh.rotation.z - this.defaultPosition.rotationZ
608
+
609
+ this.idleTween = new tweenJs.Tween(this.idleOffset, this.tween)
610
+ .to({
611
+ y: 0.05,
612
+ rotationZ: 0.05
613
+ }, 3000)
614
+ .easing(tweenJs.Easing.Sinusoidal.InOut)
615
+ .yoyo(true)
616
+ .repeat(Infinity)
617
+ .start()
618
+ }
619
+
620
+ private stopIdleAnimation() {
621
+ if (this.idleTween) {
622
+ this.idleTween.stop()
623
+ this.idleOffset.y = 0
624
+ this.idleOffset.rotationZ = 0
625
+ }
626
+ }
627
+
628
+ private getStateTransform(state: MovementState, time: number) {
629
+ switch (state) {
630
+ case 'NOT_MOVING':
631
+ case 'SNEAKING':
632
+ return {
633
+ x: this.defaultPosition.x,
634
+ y: this.defaultPosition.y,
635
+ z: this.defaultPosition.z,
636
+ rotationX: this.defaultPosition.rotationX,
637
+ rotationY: this.defaultPosition.rotationY,
638
+ rotationZ: this.defaultPosition.rotationZ
639
+ }
640
+ case 'WALKING':
641
+ case 'SPRINTING': {
642
+ const speed = state === 'SPRINTING' ? this.debugParams.sprintingSpeed : this.debugParams.walkingSpeed
643
+ const amplitude = state === 'SPRINTING' ? this.debugParams.sprintingAmplitude : this.debugParams.walkingAmplitude
644
+
645
+ return {
646
+ x: this.defaultPosition.x + Math.sin(time * speed) * amplitude.x,
647
+ y: this.defaultPosition.y - Math.abs(Math.cos(time * speed)) * amplitude.y,
648
+ z: this.defaultPosition.z,
649
+ rotationX: this.defaultPosition.rotationX,
650
+ rotationY: this.defaultPosition.rotationY,
651
+ // rotationZ: this.defaultPosition.rotationZ + Math.sin(time * speed) * amplitude.rotationZ
652
+ rotationZ: this.defaultPosition.rotationZ
653
+ }
654
+ }
655
+ }
656
+ }
657
+
658
+ setState(newState: MovementState) {
659
+ if (newState === this.targetState) return
660
+
661
+ this.targetState = newState
662
+ const noTransition = false
663
+ if (this.currentState !== newState) {
664
+ // Stop idle animation during state transitions
665
+ this.stopIdleAnimation()
666
+
667
+ // Calculate new state transform
668
+ if (!noTransition) {
669
+ // this.globalTime = 0
670
+ const stateTransform = this.getStateTransform(newState, this.globalTime)
671
+
672
+ // Start transition to new state
673
+ this.stateSwitcher.transitionTo(stateTransform, newState)
674
+ // this.updated = false
675
+ }
676
+ this.currentState = newState
677
+ }
678
+ }
679
+
680
+ updated = false
681
+ update() {
682
+ this.stateSwitcher.update()
683
+
684
+ const now = performance.now()
685
+ const deltaTime = (now - this.lastTime) / 1000
686
+ this.lastTime = now
687
+
688
+ // Update global time based on current state
689
+ if (!this.stateSwitcher.isTransitioning) {
690
+ switch (this.currentState) {
691
+ case 'NOT_MOVING':
692
+ case 'SNEAKING':
693
+ this.globalTime = Math.PI / 4
694
+ break
695
+ case 'SPRINTING':
696
+ case 'WALKING':
697
+ this.globalTime += deltaTime
698
+ break
699
+ }
700
+ }
701
+
702
+ // Check for state changes from player state
703
+ if (this.playerState) {
704
+ const newState = this.playerState.movementState
705
+ if (newState !== this.targetState) {
706
+ this.setState(newState)
707
+ }
708
+ }
709
+
710
+ // If we're not transitioning between states and in a stable state that should have idle animation
711
+ if (!this.stateSwitcher.isTransitioning &&
712
+ (this.currentState === 'NOT_MOVING' || this.currentState === 'SNEAKING')) {
713
+ // Start idle animation if not already running
714
+ if (!this.idleTween?.isPlaying()) {
715
+ this.startIdleAnimation()
716
+ }
717
+ // Update idle animation
718
+ this.tween.update()
719
+
720
+ // Apply idle offsets
721
+ this.handMesh.position.y = this.defaultPosition.y + this.idleOffset.y
722
+ this.handMesh.rotation.z = this.defaultPosition.rotationZ + this.idleOffset.rotationZ
723
+ }
724
+
725
+ // If we're in a movement state and not transitioning, update the movement animation
726
+ if (!this.stateSwitcher.isTransitioning &&
727
+ (this.currentState === 'WALKING' || this.currentState === 'SPRINTING')) {
728
+ const stateTransform = this.getStateTransform(this.currentState, this.globalTime)
729
+ Object.assign(this.handMesh.position, stateTransform)
730
+ Object.assign(this.handMesh.rotation, {
731
+ x: stateTransform.rotationX,
732
+ y: stateTransform.rotationY,
733
+ z: stateTransform.rotationZ
734
+ })
735
+ // this.stateSwitcher.transitionTo(stateTransform, this.currentState)
736
+ }
737
+ }
738
+
739
+ getCurrentState() {
740
+ return this.currentState
741
+ }
742
+
743
+ destroy() {
744
+ this.stopIdleAnimation()
745
+ this.stateSwitcher.forceFinish()
746
+ }
747
+ }
748
+
749
+ class HandSwingAnimator {
750
+ private readonly PI = Math.PI
751
+ private animationTimer = 0
752
+ private lastTime = 0
753
+ private isAnimating = false
754
+ private stopRequested = false
755
+ private readonly originalRotation: THREE.Euler
756
+ private readonly originalPosition: THREE.Vector3
757
+ private readonly originalScale: THREE.Vector3
758
+
759
+ readonly debugParams = {
760
+ // Animation timing
761
+ animationTime: 250,
762
+ animationStage: 0,
763
+ useClassicSwing: true,
764
+
765
+ // Item/Block animation parameters
766
+ itemSwingXPosScale: -0.8,
767
+ itemSwingYPosScale: 0.2,
768
+ itemSwingZPosScale: -0.2,
769
+ itemHeightScale: -0.6,
770
+ itemPreswingRotY: 45,
771
+ itemSwingXRotAmount: -30,
772
+ itemSwingYRotAmount: -35,
773
+ itemSwingZRotAmount: -5,
774
+
775
+ // Hand/Arm animation parameters
776
+ armSwingXPosScale: -0.3,
777
+ armSwingYPosScale: 0.4,
778
+ armSwingZPosScale: -0.4,
779
+ armSwingYRotAmount: 70,
780
+ armSwingZRotAmount: -20,
781
+ armHeightScale: -0.6
782
+ }
783
+
784
+ private readonly debugGui: DebugGui
785
+
786
+ public type: 'hand' | 'block' | 'item' = 'hand'
787
+
788
+ constructor(public handMesh: THREE.Object3D) {
789
+ this.handMesh = handMesh
790
+ // Store initial transforms
791
+ this.originalRotation = handMesh.rotation.clone()
792
+ this.originalPosition = handMesh.position.clone()
793
+ this.originalScale = handMesh.scale.clone()
794
+
795
+ // Initialize debug GUI
796
+ this.debugGui = new DebugGui('hand_animator', this.debugParams, undefined, {
797
+ animationStage: {
798
+ min: 0,
799
+ max: 1,
800
+ step: 0.01
801
+ },
802
+ // Add ranges for all animation parameters
803
+ itemSwingXPosScale: { min: -2, max: 2, step: 0.1 },
804
+ itemSwingYPosScale: { min: -2, max: 2, step: 0.1 },
805
+ itemSwingZPosScale: { min: -2, max: 2, step: 0.1 },
806
+ itemHeightScale: { min: -2, max: 2, step: 0.1 },
807
+ itemPreswingRotY: { min: -180, max: 180, step: 5 },
808
+ itemSwingXRotAmount: { min: -180, max: 180, step: 5 },
809
+ itemSwingYRotAmount: { min: -180, max: 180, step: 5 },
810
+ itemSwingZRotAmount: { min: -180, max: 180, step: 5 },
811
+ armSwingXPosScale: { min: -2, max: 2, step: 0.1 },
812
+ armSwingYPosScale: { min: -2, max: 2, step: 0.1 },
813
+ armSwingZPosScale: { min: -2, max: 2, step: 0.1 },
814
+ armSwingYRotAmount: { min: -180, max: 180, step: 5 },
815
+ armSwingZRotAmount: { min: -180, max: 180, step: 5 },
816
+ armHeightScale: { min: -2, max: 2, step: 0.1 }
817
+ })
818
+ // this.debugGui.activate()
819
+ }
820
+
821
+ update() {
822
+ if (!this.isAnimating && !this.debugParams.animationStage) {
823
+ // If not animating, ensure we're at original position
824
+ this.handMesh.rotation.copy(this.originalRotation)
825
+ this.handMesh.position.copy(this.originalPosition)
826
+ this.handMesh.scale.copy(this.originalScale)
827
+ return
828
+ }
829
+
830
+ const now = performance.now()
831
+ const deltaTime = (now - this.lastTime) / 1000
832
+ this.lastTime = now
833
+
834
+ // Update animation progress
835
+ this.animationTimer += deltaTime * 1000 // Convert to ms
836
+
837
+ // Calculate animation stage (0 to 1)
838
+ const stage = this.debugParams.animationStage || Math.min(this.animationTimer / this.debugParams.animationTime, 1)
839
+
840
+ if (stage >= 1) {
841
+ // Animation complete
842
+ if (this.stopRequested) {
843
+ // If stop was requested, actually stop now that we've completed a swing
844
+ this.isAnimating = false
845
+ this.stopRequested = false
846
+ this.animationTimer = 0
847
+ this.handMesh.rotation.copy(this.originalRotation)
848
+ this.handMesh.position.copy(this.originalPosition)
849
+ this.handMesh.scale.copy(this.originalScale)
850
+ return
851
+ }
852
+ // Otherwise reset timer and continue
853
+ this.animationTimer = 0
854
+ return
855
+ }
856
+
857
+ // Start from original transforms
858
+ this.handMesh.rotation.copy(this.originalRotation)
859
+ this.handMesh.position.copy(this.originalPosition)
860
+ this.handMesh.scale.copy(this.originalScale)
861
+
862
+ // Calculate swing progress
863
+ const swingProgress = stage
864
+ const sqrtProgress = Math.sqrt(swingProgress)
865
+ const sinProgress = Math.sin(swingProgress * this.PI)
866
+ const sinSqrtProgress = Math.sin(sqrtProgress * this.PI)
867
+
868
+ if (this.type === 'hand') {
869
+ // Hand animation
870
+ const xOffset = this.debugParams.armSwingXPosScale * sinSqrtProgress
871
+ const yOffset = this.debugParams.armSwingYPosScale * Math.sin(sqrtProgress * this.PI * 2)
872
+ const zOffset = this.debugParams.armSwingZPosScale * sinProgress
873
+
874
+ this.handMesh.position.x += xOffset
875
+ this.handMesh.position.y += yOffset + this.debugParams.armHeightScale * swingProgress
876
+ this.handMesh.position.z += zOffset
877
+
878
+ // Rotations
879
+ this.handMesh.rotation.y += THREE.MathUtils.degToRad(this.debugParams.armSwingYRotAmount * sinSqrtProgress)
880
+ this.handMesh.rotation.z += THREE.MathUtils.degToRad(this.debugParams.armSwingZRotAmount * sinProgress)
881
+ } else {
882
+ // Item/Block animation
883
+ const xOffset = this.debugParams.itemSwingXPosScale * sinSqrtProgress
884
+ const yOffset = this.debugParams.itemSwingYPosScale * Math.sin(sqrtProgress * this.PI * 2)
885
+ const zOffset = this.debugParams.itemSwingZPosScale * sinProgress
886
+
887
+ this.handMesh.position.x += xOffset
888
+ this.handMesh.position.y += yOffset + this.debugParams.itemHeightScale * swingProgress
889
+ this.handMesh.position.z += zOffset
890
+
891
+ // Pre-swing rotation
892
+ this.handMesh.rotation.y += THREE.MathUtils.degToRad(this.debugParams.itemPreswingRotY)
893
+
894
+ // Swing rotations
895
+ this.handMesh.rotation.x += THREE.MathUtils.degToRad(this.debugParams.itemSwingXRotAmount * sinProgress)
896
+ this.handMesh.rotation.y += THREE.MathUtils.degToRad(this.debugParams.itemSwingYRotAmount * sinSqrtProgress)
897
+ this.handMesh.rotation.z += THREE.MathUtils.degToRad(this.debugParams.itemSwingZRotAmount * sinProgress)
898
+ }
899
+ }
900
+
901
+ startSwing() {
902
+ this.stopRequested = false
903
+ if (this.isAnimating) return
904
+
905
+ this.isAnimating = true
906
+ this.animationTimer = 0
907
+ this.lastTime = performance.now()
908
+ }
909
+
910
+ stopSwing() {
911
+ if (!this.isAnimating) return
912
+ this.stopRequested = true
913
+ }
914
+
915
+ isCurrentlySwinging() {
916
+ return this.isAnimating
917
+ }
918
+ }
919
+
920
+ export const getBlockMeshFromModel = (material: THREE.Material, model: BlockModel, name: string, blockProvider: WorldBlockProvider) => {
921
+ const worldRenderModel = blockProvider.transformModel(model, {
922
+ name,
923
+ properties: {}
924
+ }) as any
925
+ return getThreeBlockModelGroup(material, [[worldRenderModel]], undefined, 'plains', loadedData)
926
+ }