minecraft-renderer 0.1.31 → 0.1.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/minecraft-renderer.js +57 -57
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +419 -419
- package/package.json +1 -1
- package/src/graphicsBackend/config.ts +1 -0
- package/src/lib/worldrendererCommon.ts +1 -1
- package/src/three/hand.ts +103 -52
- package/src/three/handLegacy.ts +90 -0
- package/src/three/holdingBlock.ts +270 -335
- package/src/three/holdingBlockFactory.ts +16 -0
- package/src/three/holdingBlockLegacy.ts +957 -0
- package/src/three/holdingBlockTypes.ts +22 -0
- package/src/three/worldRendererThree.ts +26 -5
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import * as THREE from 'three'
|
|
3
3
|
import * as tweenJs from '@tweenjs/tween.js'
|
|
4
4
|
import PrismarineItem from 'prismarine-item'
|
|
5
|
-
import
|
|
5
|
+
import { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider'
|
|
6
6
|
import { BlockModel } from 'mc-assets'
|
|
7
7
|
import { DebugGui } from '../lib/DebugGui'
|
|
8
8
|
import { SmoothSwitcher } from '../lib/smoothSwitcher'
|
|
@@ -10,85 +10,78 @@ import { watchProperty } from '../lib/utils/proxy'
|
|
|
10
10
|
import { getMyHand } from './hand'
|
|
11
11
|
import { WorldRendererThree } from './worldRendererThree'
|
|
12
12
|
import { disposeObject } from './threeJsUtils'
|
|
13
|
+
import type { IHoldingBlock } from './holdingBlockTypes'
|
|
13
14
|
import { HandItemBlock, MovementState } from '../playerState/types'
|
|
14
15
|
import { PlayerStateRenderer } from '../playerState/playerState'
|
|
15
16
|
import { getThreeBlockModelGroup } from '../mesher/standaloneRenderer'
|
|
16
17
|
import { IndexedData } from 'minecraft-data'
|
|
17
|
-
import type { ResourcesManagerTransferred } from '../resourcesManager'
|
|
18
18
|
import { WorldRendererConfig } from '../graphicsBackend'
|
|
19
|
+
import { computeCameraBob, type CameraBobInput } from '../lib/cameraBobbing'
|
|
20
|
+
|
|
21
|
+
const _tempMat = new THREE.Matrix4()
|
|
22
|
+
|
|
23
|
+
// Vanilla renderPlayerArm transform chain
|
|
24
|
+
function buildBareHandMatrix(swingProgress: number, equipProgress: number): THREE.Matrix4 {
|
|
25
|
+
const mat = new THREE.Matrix4()
|
|
26
|
+
const side = 1 // right hand
|
|
27
|
+
|
|
28
|
+
const sqrtSwing = Math.sqrt(swingProgress)
|
|
29
|
+
const swingX = -0.3 * Math.sin(sqrtSwing * Math.PI)
|
|
30
|
+
const swingY = 0.4 * Math.sin(sqrtSwing * 2 * Math.PI)
|
|
31
|
+
const swingZ = -0.4 * Math.sin(swingProgress * Math.PI)
|
|
32
|
+
|
|
33
|
+
// Step 1: Base position with swing
|
|
34
|
+
mat.multiply(_tempMat.makeTranslation(side * (swingX + 0.64), swingY - 0.6 + equipProgress * -0.6, swingZ - 0.72))
|
|
35
|
+
// Step 2: Base Y rotation 45°
|
|
36
|
+
mat.multiply(_tempMat.makeRotationY(side * 45 * Math.PI / 180))
|
|
37
|
+
// Step 3: Swing Y rotation
|
|
38
|
+
mat.multiply(_tempMat.makeRotationY(side * Math.sin(sqrtSwing * Math.PI) * 70 * Math.PI / 180))
|
|
39
|
+
// Step 4: Swing Z rotation
|
|
40
|
+
mat.multiply(_tempMat.makeRotationZ(side * Math.sin(swingProgress * swingProgress * Math.PI) * -20 * Math.PI / 180))
|
|
41
|
+
// Step 5: Second translation
|
|
42
|
+
mat.multiply(_tempMat.makeTranslation(side * -1, 3.6, 3.5))
|
|
43
|
+
// Step 6: Z rotation 120°
|
|
44
|
+
mat.multiply(_tempMat.makeRotationZ(side * 120 * Math.PI / 180))
|
|
45
|
+
// Step 7: X rotation 200°
|
|
46
|
+
mat.multiply(_tempMat.makeRotationX(200 * Math.PI / 180))
|
|
47
|
+
// Step 8: Y rotation -135°
|
|
48
|
+
mat.multiply(_tempMat.makeRotationY(side * -135 * Math.PI / 180))
|
|
49
|
+
// Step 9: Final X offset
|
|
50
|
+
mat.multiply(_tempMat.makeTranslation(side * 5.6, 0, 0))
|
|
51
|
+
// Step 10: translateToHand - arm part position (-5/16, 2/16, 0)
|
|
52
|
+
mat.multiply(_tempMat.makeTranslation(side * -5 / 16, 2 / 16, 0))
|
|
53
|
+
|
|
54
|
+
return mat
|
|
55
|
+
}
|
|
19
56
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
1.13,
|
|
46
|
-
3.2,
|
|
47
|
-
1.13
|
|
48
|
-
],
|
|
49
|
-
'scale': [
|
|
50
|
-
0.68,
|
|
51
|
-
0.68,
|
|
52
|
-
0.68
|
|
53
|
-
]
|
|
54
|
-
},
|
|
55
|
-
blockRight: {
|
|
56
|
-
'rotation': [
|
|
57
|
-
0,
|
|
58
|
-
45,
|
|
59
|
-
0
|
|
60
|
-
],
|
|
61
|
-
'translation': [
|
|
62
|
-
0,
|
|
63
|
-
0,
|
|
64
|
-
0
|
|
65
|
-
],
|
|
66
|
-
'scale': [
|
|
67
|
-
0.4,
|
|
68
|
-
0.4,
|
|
69
|
-
0.4
|
|
70
|
-
]
|
|
71
|
-
},
|
|
72
|
-
blockLeft: {
|
|
73
|
-
'rotation': [
|
|
74
|
-
0,
|
|
75
|
-
225,
|
|
76
|
-
0
|
|
77
|
-
],
|
|
78
|
-
'translation': [
|
|
79
|
-
0,
|
|
80
|
-
0,
|
|
81
|
-
0
|
|
82
|
-
],
|
|
83
|
-
'scale': [
|
|
84
|
-
0.4,
|
|
85
|
-
0.4,
|
|
86
|
-
0.4
|
|
87
|
-
]
|
|
88
|
-
}
|
|
57
|
+
// Vanilla item arm transforms: applyItemArmTransform + applyItemArmAttackTransform
|
|
58
|
+
function buildItemArmMatrix(swingProgress: number, equipProgress: number): THREE.Matrix4 {
|
|
59
|
+
const mat = new THREE.Matrix4()
|
|
60
|
+
const side = 1 // right hand
|
|
61
|
+
|
|
62
|
+
const sqrtSwing = Math.sqrt(swingProgress)
|
|
63
|
+
|
|
64
|
+
// Swing position offsets (from renderArmWithItem default branch)
|
|
65
|
+
const swingX = -0.4 * Math.sin(sqrtSwing * Math.PI)
|
|
66
|
+
const swingY = 0.2 * Math.sin(sqrtSwing * 2 * Math.PI)
|
|
67
|
+
const swingZ = -0.2 * Math.sin(swingProgress * Math.PI)
|
|
68
|
+
mat.multiply(_tempMat.makeTranslation(side * swingX, swingY, swingZ))
|
|
69
|
+
|
|
70
|
+
// applyItemArmTransform: translate(±0.56, -0.52 + equip*-0.6, -0.72)
|
|
71
|
+
mat.multiply(_tempMat.makeTranslation(side * 0.56, -0.52 + equipProgress * -0.6, -0.72))
|
|
72
|
+
|
|
73
|
+
// applyItemArmAttackTransform
|
|
74
|
+
const sinSwingSq = Math.sin(swingProgress * swingProgress * Math.PI)
|
|
75
|
+
const sinSqrtSwing = Math.sin(sqrtSwing * Math.PI)
|
|
76
|
+
mat.multiply(_tempMat.makeRotationY(side * (45 + sinSwingSq * -20) * Math.PI / 180))
|
|
77
|
+
mat.multiply(_tempMat.makeRotationZ(side * sinSqrtSwing * -20 * Math.PI / 180))
|
|
78
|
+
mat.multiply(_tempMat.makeRotationX(sinSqrtSwing * -80 * Math.PI / 180))
|
|
79
|
+
mat.multiply(_tempMat.makeRotationY(side * -45 * Math.PI / 180))
|
|
80
|
+
|
|
81
|
+
return mat
|
|
89
82
|
}
|
|
90
83
|
|
|
91
|
-
export default class HoldingBlock {
|
|
84
|
+
export default class HoldingBlock implements IHoldingBlock {
|
|
92
85
|
// TODO refactor with the tree builder for better visual understanding
|
|
93
86
|
holdingBlock: THREE.Object3D | undefined = undefined
|
|
94
87
|
blockSwapAnimation: {
|
|
@@ -96,36 +89,45 @@ export default class HoldingBlock {
|
|
|
96
89
|
// hidden: boolean
|
|
97
90
|
} | undefined = undefined
|
|
98
91
|
cameraGroup = new THREE.Mesh()
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
camera = new THREE.PerspectiveCamera(75, 1, 0.1, 100)
|
|
92
|
+
armTransformGroup = new THREE.Group()
|
|
93
|
+
camera = new THREE.PerspectiveCamera(70, 1, 0.05, 100)
|
|
94
|
+
equipProgress = 0 // 0 = fully visible, 1 = hidden
|
|
103
95
|
stopUpdate = false
|
|
104
96
|
lastHeldItem: HandItemBlock | undefined
|
|
97
|
+
currentDisplayType: 'hand' | 'item' | 'block' = 'hand'
|
|
105
98
|
isSwinging = false
|
|
106
99
|
nextIterStopCallbacks: Array<() => void> | undefined
|
|
107
100
|
idleAnimator: HandIdleAnimator | undefined
|
|
108
101
|
ready = false
|
|
109
102
|
lastUpdate = 0
|
|
103
|
+
xBob = 0
|
|
104
|
+
yBob = 0
|
|
105
|
+
lastBobUpdateTime = 0
|
|
106
|
+
private lastBobWalkDist = 0
|
|
107
|
+
private lastBobTickTime = 0
|
|
110
108
|
playerHand: THREE.Object3D | undefined
|
|
111
109
|
offHandDisplay = false
|
|
112
110
|
offHandModeLegacy = false
|
|
113
111
|
|
|
114
112
|
swingAnimator: HandSwingAnimator | undefined
|
|
115
113
|
config: WorldRendererConfig
|
|
114
|
+
private disposed = false
|
|
115
|
+
private unsubs: Array<() => void> = []
|
|
116
116
|
|
|
117
117
|
constructor(public worldRenderer: WorldRendererThree, public offHand = false) {
|
|
118
118
|
this.initCameraGroup()
|
|
119
|
-
this.
|
|
120
|
-
|
|
121
|
-
this.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
this.
|
|
127
|
-
|
|
128
|
-
|
|
119
|
+
this.unsubs.push(
|
|
120
|
+
this.worldRenderer.onReactivePlayerStateUpdated('heldItemMain', () => {
|
|
121
|
+
if (!this.offHand) {
|
|
122
|
+
this.updateItem()
|
|
123
|
+
}
|
|
124
|
+
}, false),
|
|
125
|
+
this.worldRenderer.onReactivePlayerStateUpdated('heldItemOff', () => {
|
|
126
|
+
if (this.offHand) {
|
|
127
|
+
this.updateItem()
|
|
128
|
+
}
|
|
129
|
+
}, false)
|
|
130
|
+
)
|
|
129
131
|
this.config = worldRenderer.displayOptions.inWorldRenderingConfig
|
|
130
132
|
|
|
131
133
|
this.offHandDisplay = this.offHand
|
|
@@ -133,12 +135,14 @@ export default class HoldingBlock {
|
|
|
133
135
|
if (!this.offHand) {
|
|
134
136
|
// load default hand
|
|
135
137
|
void getMyHand().then((hand) => {
|
|
138
|
+
if (this.disposed) return
|
|
136
139
|
this.playerHand = hand
|
|
137
140
|
// trigger update
|
|
138
141
|
this.updateItem()
|
|
139
142
|
}).then(() => {
|
|
143
|
+
if (this.disposed) return
|
|
140
144
|
// now watch over the player skin
|
|
141
|
-
watchProperty(
|
|
145
|
+
const unsub = watchProperty(
|
|
142
146
|
async () => {
|
|
143
147
|
return getMyHand(this.worldRenderer.playerStateReactive.playerSkin, this.worldRenderer.playerStateReactive.onlineMode ? this.worldRenderer.playerStateReactive.username : undefined)
|
|
144
148
|
},
|
|
@@ -155,10 +159,33 @@ export default class HoldingBlock {
|
|
|
155
159
|
disposeObject(oldHand!, true)
|
|
156
160
|
}
|
|
157
161
|
)
|
|
162
|
+
this.unsubs.push(unsub)
|
|
158
163
|
})
|
|
159
164
|
}
|
|
160
165
|
}
|
|
161
166
|
|
|
167
|
+
dispose() {
|
|
168
|
+
this.disposed = true
|
|
169
|
+
this.unsubs.forEach(fn => fn())
|
|
170
|
+
this.unsubs = []
|
|
171
|
+
this.idleAnimator?.destroy()
|
|
172
|
+
this.idleAnimator = undefined
|
|
173
|
+
this.swingAnimator?.stopSwing()
|
|
174
|
+
this.swingAnimator = undefined
|
|
175
|
+
this.blockSwapAnimation?.switcher.forceFinish()
|
|
176
|
+
this.blockSwapAnimation = undefined
|
|
177
|
+
disposeObject(this.cameraGroup, true)
|
|
178
|
+
if (this.holdingBlock && this.holdingBlock !== this.playerHand) {
|
|
179
|
+
disposeObject(this.holdingBlock, true)
|
|
180
|
+
}
|
|
181
|
+
this.holdingBlock = undefined
|
|
182
|
+
if (this.playerHand) {
|
|
183
|
+
disposeObject(this.playerHand, true)
|
|
184
|
+
this.playerHand = undefined
|
|
185
|
+
}
|
|
186
|
+
this.ready = false
|
|
187
|
+
}
|
|
188
|
+
|
|
162
189
|
updateItem() {
|
|
163
190
|
if (!this.ready) return
|
|
164
191
|
const item = this.offHand ? this.worldRenderer.playerStateReactive.heldItemOff : this.worldRenderer.playerStateReactive.heldItemMain
|
|
@@ -175,6 +202,9 @@ export default class HoldingBlock {
|
|
|
175
202
|
|
|
176
203
|
initCameraGroup() {
|
|
177
204
|
this.cameraGroup = new THREE.Mesh()
|
|
205
|
+
this.armTransformGroup = new THREE.Group()
|
|
206
|
+
this.armTransformGroup.matrixAutoUpdate = false
|
|
207
|
+
this.cameraGroup.add(this.armTransformGroup)
|
|
178
208
|
}
|
|
179
209
|
|
|
180
210
|
startSwing() {
|
|
@@ -192,29 +222,37 @@ export default class HoldingBlock {
|
|
|
192
222
|
void this.replaceItemModel(this.lastHeldItem)
|
|
193
223
|
}
|
|
194
224
|
|
|
195
|
-
// Only update
|
|
225
|
+
// Only update swing animation
|
|
196
226
|
if (this.swingAnimator?.isCurrentlySwinging() || this.swingAnimator?.debugParams.animationStage) {
|
|
197
227
|
this.swingAnimator?.update()
|
|
198
|
-
} else {
|
|
199
|
-
this.idleAnimator?.update()
|
|
200
228
|
}
|
|
229
|
+
// Idle animation disabled temporarily
|
|
201
230
|
|
|
202
231
|
this.blockSwapAnimation?.switcher.update()
|
|
203
232
|
|
|
204
233
|
const scene = new THREE.Scene()
|
|
205
234
|
scene.add(this.cameraGroup)
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
235
|
+
const viewerSize = renderer.getSize(new THREE.Vector2())
|
|
236
|
+
const isPortrait = viewerSize.height > viewerSize.width
|
|
237
|
+
|
|
238
|
+
if (isPortrait) {
|
|
239
|
+
// Portrait: use fixed 1:1 aspect with square viewport to keep hand visible
|
|
240
|
+
if (this.camera.aspect !== 1) {
|
|
241
|
+
this.camera.aspect = 1
|
|
242
|
+
this.camera.updateProjectionMatrix()
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
// Landscape: sync aspect with main camera (vanilla full-viewport rendering)
|
|
246
|
+
if (this.camera.aspect !== originalCamera.aspect) {
|
|
247
|
+
this.camera.aspect = originalCamera.aspect
|
|
248
|
+
this.camera.updateProjectionMatrix()
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
210
252
|
this.updateCameraGroup()
|
|
211
253
|
scene.add(ambientLight.clone())
|
|
212
254
|
scene.add(directionalLight.clone())
|
|
213
255
|
|
|
214
|
-
const viewerSize = renderer.getSize(new THREE.Vector2())
|
|
215
|
-
const minSize = Math.min(viewerSize.width, viewerSize.height)
|
|
216
|
-
const x = viewerSize.width - minSize
|
|
217
|
-
|
|
218
256
|
// Mirror the scene for offhand by scaling
|
|
219
257
|
const { offHandDisplay } = this
|
|
220
258
|
if (offHandDisplay) {
|
|
@@ -223,15 +261,23 @@ export default class HoldingBlock {
|
|
|
223
261
|
|
|
224
262
|
renderer.autoClear = false
|
|
225
263
|
renderer.clearDepth()
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
264
|
+
|
|
265
|
+
if (isPortrait) {
|
|
266
|
+
// Portrait: render in square viewport anchored to bottom-right (or bottom-left for offhand)
|
|
267
|
+
const minSize = Math.min(viewerSize.width, viewerSize.height)
|
|
268
|
+
if (offHandDisplay) {
|
|
269
|
+
renderer.setViewport(0, 0, minSize, minSize)
|
|
270
|
+
} else {
|
|
271
|
+
renderer.setViewport(viewerSize.width - minSize, 0, minSize, minSize)
|
|
272
|
+
}
|
|
232
273
|
}
|
|
274
|
+
|
|
233
275
|
renderer.render(scene, this.camera)
|
|
234
|
-
|
|
276
|
+
|
|
277
|
+
if (isPortrait) {
|
|
278
|
+
// Restore full viewport
|
|
279
|
+
renderer.setViewport(0, 0, viewerSize.width, viewerSize.height)
|
|
280
|
+
}
|
|
235
281
|
|
|
236
282
|
// Reset the mirroring after rendering
|
|
237
283
|
if (offHandDisplay) {
|
|
@@ -257,37 +303,25 @@ export default class HoldingBlock {
|
|
|
257
303
|
this.blockSwapAnimation ??= {
|
|
258
304
|
switcher: new SmoothSwitcher(
|
|
259
305
|
() => ({
|
|
260
|
-
|
|
306
|
+
progress: this.equipProgress
|
|
261
307
|
}),
|
|
262
308
|
(property, value) => {
|
|
263
|
-
if (property === '
|
|
309
|
+
if (property === 'progress') this.equipProgress = value
|
|
264
310
|
},
|
|
265
311
|
{
|
|
266
|
-
|
|
312
|
+
progress: 4 // speed: units per second
|
|
267
313
|
}
|
|
268
314
|
)
|
|
269
315
|
}
|
|
270
316
|
|
|
271
|
-
const
|
|
272
|
-
// if (forceState && newState !== forceState) {
|
|
273
|
-
// throw new Error(`forceState does not match current state ${forceState} !== ${newState}`)
|
|
274
|
-
// }
|
|
275
|
-
|
|
276
|
-
const targetY = this.objectInnerGroup.position.y + (this.objectInnerGroup.scale.y * 1.5 * (newState === 'appeared' ? 1 : -1))
|
|
277
|
-
|
|
278
|
-
// if (newState === this.blockSwapAnimation.switcher.transitioningToStateName) {
|
|
279
|
-
// return false
|
|
280
|
-
// }
|
|
281
|
-
|
|
317
|
+
const targetProgress = forceState === 'disappeared' ? 1 : 0
|
|
282
318
|
let cancelled = false
|
|
283
319
|
return new Promise<boolean>((resolve) => {
|
|
284
320
|
this.blockSwapAnimation!.switcher.transitionTo(
|
|
285
|
-
{
|
|
286
|
-
|
|
321
|
+
{ progress: targetProgress },
|
|
322
|
+
forceState,
|
|
287
323
|
() => {
|
|
288
|
-
if (!cancelled)
|
|
289
|
-
resolve(true)
|
|
290
|
-
}
|
|
324
|
+
if (!cancelled) resolve(true)
|
|
291
325
|
},
|
|
292
326
|
() => {
|
|
293
327
|
cancelled = true
|
|
@@ -316,27 +350,75 @@ export default class HoldingBlock {
|
|
|
316
350
|
updateCameraGroup() {
|
|
317
351
|
if (this.stopUpdate) return
|
|
318
352
|
const { camera } = this
|
|
353
|
+
|
|
354
|
+
// Hand rotation momentum (xBob/yBob) — vanilla Minecraft inertia effect
|
|
355
|
+
// Use base rotation from CameraShake (actual player view angles)
|
|
356
|
+
const now = performance.now()
|
|
357
|
+
const baseRotation = this.worldRenderer.cameraShake.getBaseRotation()
|
|
358
|
+
const actualPitch = baseRotation.pitch
|
|
359
|
+
const actualYaw = baseRotation.yaw
|
|
360
|
+
if (this.lastBobUpdateTime === 0) {
|
|
361
|
+
this.xBob = actualPitch
|
|
362
|
+
this.yBob = actualYaw
|
|
363
|
+
this.lastBobUpdateTime = now
|
|
364
|
+
} else {
|
|
365
|
+
const dt = Math.min((now - this.lastBobUpdateTime) / 1000, 0.1)
|
|
366
|
+
this.lastBobUpdateTime = now
|
|
367
|
+
const factor = 1 - Math.pow(0.5, dt * 20)
|
|
368
|
+
this.xBob += (actualPitch - this.xBob) * factor
|
|
369
|
+
this.yBob += (actualYaw - this.yBob) * factor
|
|
370
|
+
}
|
|
371
|
+
const pitchOffset = (actualPitch - this.xBob) * -0.1
|
|
372
|
+
const yawOffset = (actualYaw - this.yBob) * -0.1
|
|
373
|
+
|
|
319
374
|
this.cameraGroup.position.copy(camera.position)
|
|
320
375
|
this.cameraGroup.rotation.copy(camera.rotation)
|
|
321
376
|
|
|
322
|
-
//
|
|
323
|
-
|
|
324
|
-
|
|
377
|
+
// ─── bobView: Walking hand bob (vanilla-accurate) ───
|
|
378
|
+
const viewBobbing = this.worldRenderer.displayOptions.inWorldRenderingConfig.viewBobbing
|
|
379
|
+
if (viewBobbing) {
|
|
380
|
+
const ps = this.worldRenderer.playerStateReactive
|
|
325
381
|
|
|
382
|
+
// Track tick timing for partialTick (same approach as CameraBobbingModule)
|
|
383
|
+
if (ps.walkDist !== this.lastBobWalkDist) {
|
|
384
|
+
this.lastBobTickTime = now
|
|
385
|
+
this.lastBobWalkDist = ps.walkDist
|
|
386
|
+
}
|
|
387
|
+
const partialTick = Math.min((now - this.lastBobTickTime) / 50, 1)
|
|
388
|
+
|
|
389
|
+
const handBobSpeedMultiplier = 1.8
|
|
390
|
+
const bob = computeCameraBob({
|
|
391
|
+
walkDist: ps.walkDist * handBobSpeedMultiplier,
|
|
392
|
+
prevWalkDist: ps.prevWalkDist * handBobSpeedMultiplier,
|
|
393
|
+
bob: ps.bob,
|
|
394
|
+
prevBob: ps.prevBob,
|
|
395
|
+
partialTick
|
|
396
|
+
})
|
|
326
397
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
)
|
|
398
|
+
// Apply bobView position (translate)
|
|
399
|
+
this.cameraGroup.position.x += bob.position.x
|
|
400
|
+
this.cameraGroup.position.y += bob.position.y
|
|
401
|
+
|
|
402
|
+
// Apply bobView rotation (roll Z + pitch X)
|
|
403
|
+
this.cameraGroup.rotation.z += bob.rotation.z
|
|
404
|
+
this.cameraGroup.rotation.x += bob.rotation.x
|
|
405
|
+
}
|
|
336
406
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
407
|
+
this.cameraGroup.rotation.x += pitchOffset
|
|
408
|
+
this.cameraGroup.rotation.y += yawOffset
|
|
409
|
+
|
|
410
|
+
const type = this.currentDisplayType
|
|
411
|
+
const swingProgress = this.swingAnimator?.getSwingProgress() ?? 0
|
|
412
|
+
|
|
413
|
+
let matrix: THREE.Matrix4
|
|
414
|
+
if (type === 'hand') {
|
|
415
|
+
matrix = buildBareHandMatrix(swingProgress, this.equipProgress)
|
|
416
|
+
} else {
|
|
417
|
+
matrix = buildItemArmMatrix(swingProgress, this.equipProgress)
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
this.armTransformGroup.matrix.copy(matrix)
|
|
421
|
+
this.armTransformGroup.matrixWorldNeedsUpdate = true
|
|
340
422
|
}
|
|
341
423
|
|
|
342
424
|
lastItemModelName: string | undefined
|
|
@@ -360,7 +442,6 @@ export default class HoldingBlock {
|
|
|
360
442
|
blockInner = itemMesh
|
|
361
443
|
handItem.type = 'block'
|
|
362
444
|
} else {
|
|
363
|
-
itemMesh.position.set(0.5, 0.5, 0.5)
|
|
364
445
|
blockInner = itemMesh
|
|
365
446
|
handItem.type = 'item'
|
|
366
447
|
}
|
|
@@ -370,12 +451,30 @@ export default class HoldingBlock {
|
|
|
370
451
|
blockInner = this.playerHand!
|
|
371
452
|
}
|
|
372
453
|
if (!blockInner) return
|
|
373
|
-
blockInner.name = 'holdingBlock'
|
|
374
454
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
455
|
+
// Apply vanilla firstperson_righthand display transforms (ItemTransform.apply)
|
|
456
|
+
// Vanilla order: translate(÷16) → rotateXYZ → scale, then model centering
|
|
457
|
+
if (handItem.type === 'item' || handItem.type === 'block') {
|
|
458
|
+
const displayGroup = new THREE.Group()
|
|
459
|
+
displayGroup.name = 'displayTransform'
|
|
460
|
+
|
|
461
|
+
if (handItem.type === 'item') {
|
|
462
|
+
// Vanilla item/handheld firstperson_righthand defaults
|
|
463
|
+
// Translation pre-divided by 16 per ItemTransform deserialization
|
|
464
|
+
displayGroup.position.set(1.13 / 16, 3.2 / 16, 1.13 / 16)
|
|
465
|
+
displayGroup.rotation.set(0, THREE.MathUtils.degToRad(-90), THREE.MathUtils.degToRad(25), 'XYZ')
|
|
466
|
+
displayGroup.scale.set(0.68, 0.68, 0.68)
|
|
467
|
+
} else {
|
|
468
|
+
// Vanilla block/block firstperson_righthand defaults
|
|
469
|
+
displayGroup.rotation.set(0, THREE.MathUtils.degToRad(45), 0, 'XYZ')
|
|
470
|
+
displayGroup.scale.set(0.4, 0.4, 0.4)
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
displayGroup.add(blockInner)
|
|
474
|
+
blockInner = displayGroup
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
blockInner.name = 'holdingBlock'
|
|
379
478
|
|
|
380
479
|
return { model: blockInner, type: handItem.type }
|
|
381
480
|
}
|
|
@@ -387,6 +486,7 @@ export default class HoldingBlock {
|
|
|
387
486
|
if (!handItem) {
|
|
388
487
|
this.holdingBlock?.removeFromParent()
|
|
389
488
|
this.holdingBlock = undefined
|
|
489
|
+
this.currentDisplayType = 'hand'
|
|
390
490
|
this.swingAnimator?.stopSwing()
|
|
391
491
|
this.swingAnimator = undefined
|
|
392
492
|
this.idleAnimator = undefined
|
|
@@ -399,7 +499,8 @@ export default class HoldingBlock {
|
|
|
399
499
|
// Update the model without changing the group structure
|
|
400
500
|
this.holdingBlock?.removeFromParent()
|
|
401
501
|
this.holdingBlock = result.model
|
|
402
|
-
this.
|
|
502
|
+
this.currentDisplayType = result.type
|
|
503
|
+
this.armTransformGroup.add(result.model)
|
|
403
504
|
|
|
404
505
|
|
|
405
506
|
}
|
|
@@ -419,18 +520,21 @@ export default class HoldingBlock {
|
|
|
419
520
|
this.lastItemModelName = undefined
|
|
420
521
|
const switchRequest = ++this.switchRequest
|
|
421
522
|
this.lastHeldItem = handItem
|
|
523
|
+
|
|
422
524
|
let playAppearAnimation = false
|
|
423
525
|
if (this.holdingBlock) {
|
|
424
|
-
// play disappear animation
|
|
425
526
|
playAppearAnimation = true
|
|
426
527
|
const result = await this.playBlockSwapAnimation('disappeared')
|
|
427
528
|
if (!result) return
|
|
428
|
-
this.holdingBlock
|
|
529
|
+
this.holdingBlock.removeFromParent()
|
|
530
|
+
if (this.holdingBlock !== this.playerHand) {
|
|
531
|
+
disposeObject(this.holdingBlock, false)
|
|
532
|
+
}
|
|
429
533
|
this.holdingBlock = undefined
|
|
430
534
|
}
|
|
431
535
|
|
|
432
536
|
if (!handItem) {
|
|
433
|
-
this.
|
|
537
|
+
this.currentDisplayType = 'hand'
|
|
434
538
|
this.swingAnimator = undefined
|
|
435
539
|
this.idleAnimator = undefined
|
|
436
540
|
this.blockSwapAnimation = undefined
|
|
@@ -441,91 +545,20 @@ export default class HoldingBlock {
|
|
|
441
545
|
const result = await this.createItemModel(handItem)
|
|
442
546
|
if (!result || switchRequest !== this.switchRequest) return
|
|
443
547
|
|
|
444
|
-
const blockOuterGroup = new THREE.Group()
|
|
445
|
-
this.holdingBlockInnerGroup.removeFromParent()
|
|
446
|
-
this.holdingBlockInnerGroup = new THREE.Group()
|
|
447
|
-
this.holdingBlockInnerGroup.add(result.model)
|
|
448
|
-
blockOuterGroup.add(this.holdingBlockInnerGroup)
|
|
449
548
|
this.holdingBlock = result.model
|
|
450
|
-
this.
|
|
451
|
-
this.
|
|
452
|
-
this.objectInnerGroup.position.set(-0.5, -0.5, -0.5)
|
|
453
|
-
if (playAppearAnimation) {
|
|
454
|
-
this.objectInnerGroup.position.y -= this.objectInnerGroup.scale.y * 1.5
|
|
455
|
-
}
|
|
456
|
-
Object.assign(blockOuterGroup.position, { x: 0.5, y: 0.5, z: 0.5 })
|
|
457
|
-
|
|
458
|
-
this.objectOuterGroup = new THREE.Group()
|
|
459
|
-
this.objectOuterGroup.add(this.objectInnerGroup)
|
|
460
|
-
|
|
461
|
-
this.cameraGroup.add(this.objectOuterGroup)
|
|
462
|
-
const rotationDeg = this.getHandHeld3d().rotation
|
|
463
|
-
this.objectOuterGroup.rotation.y = THREE.MathUtils.degToRad(rotationDeg.yOuter)
|
|
549
|
+
this.currentDisplayType = result.type
|
|
550
|
+
this.armTransformGroup.add(this.holdingBlock)
|
|
464
551
|
|
|
465
552
|
if (playAppearAnimation) {
|
|
466
553
|
await this.playBlockSwapAnimation('appeared')
|
|
467
554
|
}
|
|
468
555
|
|
|
469
|
-
this.swingAnimator = new HandSwingAnimator(
|
|
556
|
+
this.swingAnimator = new HandSwingAnimator()
|
|
470
557
|
this.swingAnimator.type = result.type
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
}
|
|
558
|
+
// Idle animation disabled — walking bob is handled by vanilla bobView applied to cameraGroup
|
|
559
|
+
this.idleAnimator = undefined
|
|
474
560
|
}
|
|
475
561
|
|
|
476
|
-
getHandHeld3d() {
|
|
477
|
-
const type = this.lastHeldItem?.type ?? 'hand'
|
|
478
|
-
const side = this.offHandModeLegacy ? 'Left' : 'Right'
|
|
479
|
-
|
|
480
|
-
let scale = 0.8 * 1.15 // default scale for hand
|
|
481
|
-
let position = {
|
|
482
|
-
x: 0.4,
|
|
483
|
-
y: -0.7,
|
|
484
|
-
z: -0.45
|
|
485
|
-
}
|
|
486
|
-
let rotation = {
|
|
487
|
-
x: -32.4,
|
|
488
|
-
y: 42.8,
|
|
489
|
-
z: -41.3,
|
|
490
|
-
yOuter: 0
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
if (type === 'item') {
|
|
494
|
-
const itemData = rotationPositionData[`item${side}`]
|
|
495
|
-
position = {
|
|
496
|
-
x: -0.05,
|
|
497
|
-
y: -0.7,
|
|
498
|
-
z: -0.45
|
|
499
|
-
}
|
|
500
|
-
rotation = {
|
|
501
|
-
x: itemData.rotation[0],
|
|
502
|
-
y: itemData.rotation[1],
|
|
503
|
-
z: itemData.rotation[2],
|
|
504
|
-
yOuter: 0
|
|
505
|
-
}
|
|
506
|
-
scale = itemData.scale[0] * 1.15
|
|
507
|
-
} else if (type === 'block') {
|
|
508
|
-
const blockData = rotationPositionData[`block${side}`]
|
|
509
|
-
position = {
|
|
510
|
-
x: 0.4,
|
|
511
|
-
y: -0.7,
|
|
512
|
-
z: -0.45
|
|
513
|
-
}
|
|
514
|
-
rotation = {
|
|
515
|
-
x: blockData.rotation[0],
|
|
516
|
-
y: blockData.rotation[1],
|
|
517
|
-
z: blockData.rotation[2],
|
|
518
|
-
yOuter: 0
|
|
519
|
-
}
|
|
520
|
-
scale = blockData.scale[0] * 1.15
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
return {
|
|
524
|
-
rotation,
|
|
525
|
-
position,
|
|
526
|
-
scale
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
562
|
}
|
|
530
563
|
|
|
531
564
|
class HandIdleAnimator {
|
|
@@ -750,83 +783,29 @@ class HandIdleAnimator {
|
|
|
750
783
|
}
|
|
751
784
|
|
|
752
785
|
class HandSwingAnimator {
|
|
753
|
-
private readonly PI = Math.PI
|
|
754
786
|
private animationTimer = 0
|
|
755
787
|
private lastTime = 0
|
|
756
788
|
private isAnimating = false
|
|
757
789
|
private stopRequested = false
|
|
758
|
-
private
|
|
759
|
-
|
|
760
|
-
private readonly originalScale: THREE.Vector3
|
|
790
|
+
private swingProgress = 0
|
|
791
|
+
public type: 'hand' | 'block' | 'item' = 'hand'
|
|
761
792
|
|
|
762
793
|
readonly debugParams = {
|
|
763
|
-
// Animation timing
|
|
764
794
|
animationTime: 250,
|
|
765
795
|
animationStage: 0,
|
|
766
|
-
useClassicSwing: true,
|
|
767
|
-
|
|
768
|
-
// Item/Block animation parameters
|
|
769
|
-
itemSwingXPosScale: -0.8,
|
|
770
|
-
itemSwingYPosScale: 0.2,
|
|
771
|
-
itemSwingZPosScale: -0.2,
|
|
772
|
-
itemHeightScale: -0.6,
|
|
773
|
-
itemPreswingRotY: 45,
|
|
774
|
-
itemSwingXRotAmount: -30,
|
|
775
|
-
itemSwingYRotAmount: -35,
|
|
776
|
-
itemSwingZRotAmount: -5,
|
|
777
|
-
|
|
778
|
-
// Hand/Arm animation parameters
|
|
779
|
-
armSwingXPosScale: -0.3,
|
|
780
|
-
armSwingYPosScale: 0.4,
|
|
781
|
-
armSwingZPosScale: -0.4,
|
|
782
|
-
armSwingYRotAmount: 70,
|
|
783
|
-
armSwingZRotAmount: -20,
|
|
784
|
-
armHeightScale: -0.6
|
|
785
796
|
}
|
|
786
797
|
|
|
787
798
|
private readonly debugGui: DebugGui
|
|
788
799
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
constructor(public handMesh: THREE.Object3D) {
|
|
792
|
-
this.handMesh = handMesh
|
|
793
|
-
// Store initial transforms
|
|
794
|
-
this.originalRotation = handMesh.rotation.clone()
|
|
795
|
-
this.originalPosition = handMesh.position.clone()
|
|
796
|
-
this.originalScale = handMesh.scale.clone()
|
|
797
|
-
|
|
798
|
-
// Initialize debug GUI
|
|
800
|
+
constructor() {
|
|
799
801
|
this.debugGui = new DebugGui('hand_animator', this.debugParams, undefined, {
|
|
800
|
-
animationStage: {
|
|
801
|
-
min: 0,
|
|
802
|
-
max: 1,
|
|
803
|
-
step: 0.01
|
|
804
|
-
},
|
|
805
|
-
// Add ranges for all animation parameters
|
|
806
|
-
itemSwingXPosScale: { min: -2, max: 2, step: 0.1 },
|
|
807
|
-
itemSwingYPosScale: { min: -2, max: 2, step: 0.1 },
|
|
808
|
-
itemSwingZPosScale: { min: -2, max: 2, step: 0.1 },
|
|
809
|
-
itemHeightScale: { min: -2, max: 2, step: 0.1 },
|
|
810
|
-
itemPreswingRotY: { min: -180, max: 180, step: 5 },
|
|
811
|
-
itemSwingXRotAmount: { min: -180, max: 180, step: 5 },
|
|
812
|
-
itemSwingYRotAmount: { min: -180, max: 180, step: 5 },
|
|
813
|
-
itemSwingZRotAmount: { min: -180, max: 180, step: 5 },
|
|
814
|
-
armSwingXPosScale: { min: -2, max: 2, step: 0.1 },
|
|
815
|
-
armSwingYPosScale: { min: -2, max: 2, step: 0.1 },
|
|
816
|
-
armSwingZPosScale: { min: -2, max: 2, step: 0.1 },
|
|
817
|
-
armSwingYRotAmount: { min: -180, max: 180, step: 5 },
|
|
818
|
-
armSwingZRotAmount: { min: -180, max: 180, step: 5 },
|
|
819
|
-
armHeightScale: { min: -2, max: 2, step: 0.1 }
|
|
802
|
+
animationStage: { min: 0, max: 1, step: 0.01 },
|
|
820
803
|
})
|
|
821
|
-
// this.debugGui.activate()
|
|
822
804
|
}
|
|
823
805
|
|
|
824
806
|
update() {
|
|
825
807
|
if (!this.isAnimating && !this.debugParams.animationStage) {
|
|
826
|
-
|
|
827
|
-
this.handMesh.rotation.copy(this.originalRotation)
|
|
828
|
-
this.handMesh.position.copy(this.originalPosition)
|
|
829
|
-
this.handMesh.scale.copy(this.originalScale)
|
|
808
|
+
this.swingProgress = 0
|
|
830
809
|
return
|
|
831
810
|
}
|
|
832
811
|
|
|
@@ -834,77 +813,33 @@ class HandSwingAnimator {
|
|
|
834
813
|
const deltaTime = (now - this.lastTime) / 1000
|
|
835
814
|
this.lastTime = now
|
|
836
815
|
|
|
837
|
-
|
|
838
|
-
this.animationTimer += deltaTime * 1000 // Convert to ms
|
|
816
|
+
this.animationTimer += deltaTime * 1000
|
|
839
817
|
|
|
840
|
-
// Calculate animation stage (0 to 1)
|
|
841
818
|
const stage = this.debugParams.animationStage || Math.min(this.animationTimer / this.debugParams.animationTime, 1)
|
|
842
819
|
|
|
843
820
|
if (stage >= 1) {
|
|
844
|
-
// Animation complete
|
|
845
821
|
if (this.stopRequested) {
|
|
846
|
-
// If stop was requested, actually stop now that we've completed a swing
|
|
847
822
|
this.isAnimating = false
|
|
848
823
|
this.stopRequested = false
|
|
849
824
|
this.animationTimer = 0
|
|
850
|
-
this.
|
|
851
|
-
this.handMesh.position.copy(this.originalPosition)
|
|
852
|
-
this.handMesh.scale.copy(this.originalScale)
|
|
825
|
+
this.swingProgress = 0
|
|
853
826
|
return
|
|
854
827
|
}
|
|
855
|
-
// Otherwise reset timer and continue
|
|
856
828
|
this.animationTimer = 0
|
|
829
|
+
this.swingProgress = 0
|
|
857
830
|
return
|
|
858
831
|
}
|
|
859
832
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
this.handMesh.position.copy(this.originalPosition)
|
|
863
|
-
this.handMesh.scale.copy(this.originalScale)
|
|
864
|
-
|
|
865
|
-
// Calculate swing progress
|
|
866
|
-
const swingProgress = stage
|
|
867
|
-
const sqrtProgress = Math.sqrt(swingProgress)
|
|
868
|
-
const sinProgress = Math.sin(swingProgress * this.PI)
|
|
869
|
-
const sinSqrtProgress = Math.sin(sqrtProgress * this.PI)
|
|
870
|
-
|
|
871
|
-
if (this.type === 'hand') {
|
|
872
|
-
// Hand animation
|
|
873
|
-
const xOffset = this.debugParams.armSwingXPosScale * sinSqrtProgress
|
|
874
|
-
const yOffset = this.debugParams.armSwingYPosScale * Math.sin(sqrtProgress * this.PI * 2)
|
|
875
|
-
const zOffset = this.debugParams.armSwingZPosScale * sinProgress
|
|
876
|
-
|
|
877
|
-
this.handMesh.position.x += xOffset
|
|
878
|
-
this.handMesh.position.y += yOffset + this.debugParams.armHeightScale * swingProgress
|
|
879
|
-
this.handMesh.position.z += zOffset
|
|
880
|
-
|
|
881
|
-
// Rotations
|
|
882
|
-
this.handMesh.rotation.y += THREE.MathUtils.degToRad(this.debugParams.armSwingYRotAmount * sinSqrtProgress)
|
|
883
|
-
this.handMesh.rotation.z += THREE.MathUtils.degToRad(this.debugParams.armSwingZRotAmount * sinProgress)
|
|
884
|
-
} else {
|
|
885
|
-
// Item/Block animation
|
|
886
|
-
const xOffset = this.debugParams.itemSwingXPosScale * sinSqrtProgress
|
|
887
|
-
const yOffset = this.debugParams.itemSwingYPosScale * Math.sin(sqrtProgress * this.PI * 2)
|
|
888
|
-
const zOffset = this.debugParams.itemSwingZPosScale * sinProgress
|
|
889
|
-
|
|
890
|
-
this.handMesh.position.x += xOffset
|
|
891
|
-
this.handMesh.position.y += yOffset + this.debugParams.itemHeightScale * swingProgress
|
|
892
|
-
this.handMesh.position.z += zOffset
|
|
893
|
-
|
|
894
|
-
// Pre-swing rotation
|
|
895
|
-
this.handMesh.rotation.y += THREE.MathUtils.degToRad(this.debugParams.itemPreswingRotY)
|
|
833
|
+
this.swingProgress = stage
|
|
834
|
+
}
|
|
896
835
|
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
this.handMesh.rotation.y += THREE.MathUtils.degToRad(this.debugParams.itemSwingYRotAmount * sinSqrtProgress)
|
|
900
|
-
this.handMesh.rotation.z += THREE.MathUtils.degToRad(this.debugParams.itemSwingZRotAmount * sinProgress)
|
|
901
|
-
}
|
|
836
|
+
getSwingProgress(): number {
|
|
837
|
+
return this.swingProgress
|
|
902
838
|
}
|
|
903
839
|
|
|
904
840
|
startSwing() {
|
|
905
841
|
this.stopRequested = false
|
|
906
842
|
if (this.isAnimating) return
|
|
907
|
-
|
|
908
843
|
this.isAnimating = true
|
|
909
844
|
this.animationTimer = 0
|
|
910
845
|
this.lastTime = performance.now()
|