minecraft-renderer 0.1.93 → 0.1.95
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 +64 -64
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +522 -522
- package/package.json +1 -1
- package/src/graphicsBackend/appViewer.ts +4 -3
- package/src/graphicsBackend/types.ts +4 -1
- package/src/lib/worldrendererCommon.ts +2 -2
- package/src/three/entities.ts +303 -33
- package/src/three/entity/EntityMesh.ts +8 -3
- package/src/three/entity/animations.js +37 -25
- package/src/three/entity/animations.test.ts +249 -0
- package/src/three/entity/boatModelRotation.test.ts +29 -0
- package/src/three/entity/boatModelRotation.ts +14 -0
- package/src/three/entity/boatPaddleAnimation.test.ts +108 -0
- package/src/three/entity/boatPaddleAnimation.ts +104 -0
- package/src/three/entity/boatPassengerRendering.ts +20 -0
- package/src/three/entity/boatPassengerRotation.test.ts +166 -0
- package/src/three/entity/boatPassengerRotation.ts +71 -0
- package/src/three/entity/boatRenderSetup.test.ts +236 -0
- package/src/three/entity/boatRenderSetup.ts +214 -0
- package/src/three/entity/entityHeadPose.test.ts +15 -0
- package/src/three/entity/entityHeadPose.ts +22 -0
- package/src/three/entity/horseEntityScale.test.ts +11 -0
- package/src/three/entity/horseEntityScale.ts +9 -0
- package/src/three/entity/horseHeadAnimation.test.ts +159 -0
- package/src/three/entity/horseHeadAnimation.ts +151 -0
- package/src/three/entity/horsePassengerRendering.test.ts +77 -0
- package/src/three/entity/interpolationPolicy.test.ts +281 -0
- package/src/three/entity/interpolationPolicy.ts +242 -0
- package/src/three/entity/localHorseCameraYawLock.test.ts +72 -0
- package/src/three/entity/localVehicleRendering.integration.test.ts +267 -0
- package/src/three/entity/minecartLocalPassengerRendering.test.ts +210 -0
- package/src/three/entity/networkHeadPitchRendering.test.ts +102 -0
- package/src/three/entity/networkHeadPitchRendering.ts +44 -0
- package/src/three/entity/remoteBoatPassengerRotation.integration.test.ts +229 -0
- package/src/three/entity/remoteBoatPassengerRotation.ts +128 -0
- package/src/three/entity/vehiclePassengerProduction.test.ts +229 -0
- package/src/three/entity/vehiclePassengerRendering.ts +56 -0
- package/src/three/entity/vehiclePassengerUpdate.ts +91 -0
- package/src/three/graphicsBackendBase.ts +5 -5
- package/src/three/graphicsBackendOffThread.ts +2 -2
- package/src/three/worldRendererThree.ts +48 -22
package/package.json
CHANGED
|
@@ -19,7 +19,8 @@ import type {
|
|
|
19
19
|
GraphicsInitOptions,
|
|
20
20
|
DisplayWorldOptions,
|
|
21
21
|
RendererReactiveState,
|
|
22
|
-
NonReactiveState
|
|
22
|
+
NonReactiveState,
|
|
23
|
+
UpdateCameraOptions
|
|
23
24
|
} from './types'
|
|
24
25
|
import { WorldView, WorldProvider, WorldViewWorker } from '../worldView'
|
|
25
26
|
import { getInitialPlayerState } from './playerState'
|
|
@@ -274,8 +275,8 @@ export class AppViewer {
|
|
|
274
275
|
/**
|
|
275
276
|
* Update camera position and rotation.
|
|
276
277
|
*/
|
|
277
|
-
updateCamera(pos: Vec3 | null, yaw: number, pitch: number): void {
|
|
278
|
-
this.backend?.updateCamera(pos, yaw, pitch)
|
|
278
|
+
updateCamera(pos: Vec3 | null, yaw: number, pitch: number, options?: UpdateCameraOptions): void {
|
|
279
|
+
this.backend?.updateCamera(pos, yaw, pitch, options)
|
|
279
280
|
}
|
|
280
281
|
|
|
281
282
|
/**
|
|
@@ -10,6 +10,9 @@ import { ResourcesManagerTransferred } from '../resourcesManager'
|
|
|
10
10
|
import { WorldViewWorker } from '../worldView'
|
|
11
11
|
import { Vec3 } from 'vec3'
|
|
12
12
|
import { WorldRendererConfig } from './config'
|
|
13
|
+
import type { UpdateCameraOptions } from '../three/entity/interpolationPolicy'
|
|
14
|
+
|
|
15
|
+
export type { CameraMovementMode, UpdateCameraOptions } from '../three/entity/interpolationPolicy'
|
|
13
16
|
|
|
14
17
|
// ============================================================================
|
|
15
18
|
// Graphics Backend Configuration
|
|
@@ -135,7 +138,7 @@ export interface GraphicsBackend {
|
|
|
135
138
|
startWorld(options: DisplayWorldOptions): Promise<void>
|
|
136
139
|
disconnect(): void
|
|
137
140
|
setRendering(rendering: boolean): void
|
|
138
|
-
updateCamera(pos: Vec3 | null, yaw: number, pitch: number): void
|
|
141
|
+
updateCamera(pos: Vec3 | null, yaw: number, pitch: number, options?: UpdateCameraOptions): void
|
|
139
142
|
soundSystem?: any
|
|
140
143
|
backendMethods?: any
|
|
141
144
|
getDebugOverlay?(): { entitiesString?: string; left?: Record<string, string>; right?: Record<string, string> }
|
|
@@ -8,7 +8,7 @@ import { subscribeKey } from 'valtio/utils'
|
|
|
8
8
|
import { proxy, subscribe } from 'valtio'
|
|
9
9
|
import type { ResourcesManagerTransferred } from '../resourcesManager/resourcesManager'
|
|
10
10
|
import { dynamicMcDataFiles } from './buildSharedConfig.mjs'
|
|
11
|
-
import { DisplayWorldOptions, GraphicsInitOptions, RendererReactiveState, SoundSystem } from '../graphicsBackend/types'
|
|
11
|
+
import { DisplayWorldOptions, GraphicsInitOptions, RendererReactiveState, SoundSystem, UpdateCameraOptions } from '../graphicsBackend/types'
|
|
12
12
|
import {
|
|
13
13
|
HighestBlockInfo,
|
|
14
14
|
CustomBlockModels,
|
|
@@ -713,7 +713,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
713
713
|
|
|
714
714
|
abstract handleWorkerMessage(data: WorkerReceive): void
|
|
715
715
|
|
|
716
|
-
abstract updateCamera(pos: Vec3 | null, yaw: number, pitch: number): void
|
|
716
|
+
abstract updateCamera(pos: Vec3 | null, yaw: number, pitch: number, options?: UpdateCameraOptions): void
|
|
717
717
|
|
|
718
718
|
abstract render(): void
|
|
719
719
|
|
package/src/three/entities.ts
CHANGED
|
@@ -20,7 +20,44 @@ import { configurePlayerSkinMaterials, PlayerObjectType } from '../lib/createPla
|
|
|
20
20
|
import { getBlockMeshFromModel } from './holdingBlock'
|
|
21
21
|
import { createItemMesh } from './itemMesh'
|
|
22
22
|
import * as Entity from './entity/EntityMesh'
|
|
23
|
+
import { setupBoatMesh, disposeBoatWaterPatch } from './entity/boatRenderSetup'
|
|
24
|
+
import { getBoatMeshYawOffset, isBoatEntityName } from './entity/boatModelRotation'
|
|
25
|
+
import { resolveBoatPassengerThirdPersonRotation, shouldApplyBoatPassengerThirdPersonRotation } from './entity/boatPassengerRotation'
|
|
26
|
+
import {
|
|
27
|
+
createBoatPaddleAnimationState,
|
|
28
|
+
createBoatPaddlePivotScratch,
|
|
29
|
+
syncBoatPaddleAnimationTargets,
|
|
30
|
+
updateBoatPaddleAnimationState,
|
|
31
|
+
type BoatPaddleAnimationState
|
|
32
|
+
} from './entity/boatPaddleAnimation'
|
|
33
|
+
import { releaseVehiclePassengerPosition } from './entity/vehiclePassengerRendering'
|
|
34
|
+
import { updateVehiclePassengerPositions as applyVehiclePassengerPositions } from './entity/vehiclePassengerUpdate'
|
|
35
|
+
import { applyNetworkHeadPitch, storeNetworkHeadPitch, storeNetworkHeadYaw } from './entity/networkHeadPitchRendering'
|
|
36
|
+
import { processRemoteBoatPassengerRotations, type RemoteBoatPassengerEntity } from './entity/remoteBoatPassengerRotation'
|
|
37
|
+
import {
|
|
38
|
+
ENTITY_TWEEN_DURATION_MS,
|
|
39
|
+
applyLocalHorseCameraYawLock,
|
|
40
|
+
getEntityTweenDurationMs,
|
|
41
|
+
getEntityRotationTweenDurationMs,
|
|
42
|
+
resolveLocalVehicleWorldPosition,
|
|
43
|
+
shouldApplyLocalHorseCameraYawLock,
|
|
44
|
+
type EntityRenderHints,
|
|
45
|
+
type Vec3Like,
|
|
46
|
+
usesCameraSyncedVehiclePosition,
|
|
47
|
+
isRideableMinecartEntityName
|
|
48
|
+
} from './entity/interpolationPolicy'
|
|
23
49
|
import { getMesh } from './entity/EntityMesh'
|
|
50
|
+
import {
|
|
51
|
+
HORSE_HEAD_ANIMATION_USER_DATA_KEY,
|
|
52
|
+
HORSE_HEAD_RIG_USER_DATA_KEY,
|
|
53
|
+
applyHorseHeadPose,
|
|
54
|
+
calculateHorseHeadPose,
|
|
55
|
+
createHorseHeadAnimationState,
|
|
56
|
+
setHorseHeadAnimationPosition,
|
|
57
|
+
updateHorseHeadAnimationFrame,
|
|
58
|
+
type HorseHeadAnimationState,
|
|
59
|
+
type HorseHeadRig
|
|
60
|
+
} from './entity/horseHeadAnimation'
|
|
24
61
|
import { WalkingGeneralSwing } from './entity/animations'
|
|
25
62
|
import { disposeObject, loadNearestFilterTexture, loadTexture, loadThreeJsTextureFromUrl } from './threeJsUtils'
|
|
26
63
|
import { armorModel, armorTextures, elytraTexture } from './entity/armorModels'
|
|
@@ -41,7 +78,7 @@ type EntityMetadataVersions = {
|
|
|
41
78
|
|
|
42
79
|
export const steveTexture = loadThreeJsTextureFromUrl(stevePngUrl)
|
|
43
80
|
|
|
44
|
-
export const TWEEN_DURATION =
|
|
81
|
+
export const TWEEN_DURATION = ENTITY_TWEEN_DURATION_MS
|
|
45
82
|
|
|
46
83
|
const degreesToRadians = (degrees: number) => degrees * (Math.PI / 180)
|
|
47
84
|
|
|
@@ -265,7 +302,7 @@ export type SceneEntity = THREE.Object3D & {
|
|
|
265
302
|
username?: string
|
|
266
303
|
uuid?: string
|
|
267
304
|
additionalCleanup?: () => void
|
|
268
|
-
originalEntity: import('prismarine-entity').Entity & { delete?; pos?; name; team?: Team }
|
|
305
|
+
originalEntity: import('prismarine-entity').Entity & { delete?; pos?; name; team?: Team; renderHints?: EntityRenderHints }
|
|
269
306
|
}
|
|
270
307
|
|
|
271
308
|
export class Entities {
|
|
@@ -281,6 +318,7 @@ export class Entities {
|
|
|
281
318
|
cachedMapsImages = {} as Record<number, string>
|
|
282
319
|
itemFrameMaps = {} as Record<number, Array<THREE.Mesh<THREE.PlaneGeometry, THREE.MeshLambertMaterial>>>
|
|
283
320
|
pendingModelOverrides = new Map<string, { parts: EntityModelOverridePart[] }>()
|
|
321
|
+
private boatPaddleScratch = createBoatPaddlePivotScratch()
|
|
284
322
|
|
|
285
323
|
get entitiesByName(): Record<string, SceneEntity[]> {
|
|
286
324
|
const byName: Record<string, SceneEntity[]> = {}
|
|
@@ -450,7 +488,8 @@ export class Entities {
|
|
|
450
488
|
const thirdPerson = this.worldRenderer.playerStateUtils.isThirdPerson()
|
|
451
489
|
entity.visible = thirdPerson
|
|
452
490
|
|
|
453
|
-
|
|
491
|
+
const isAnchoredPassenger = entity.userData._passengerVehicleId != null || entity.userData._boatPassengerVehicleId != null
|
|
492
|
+
if (thirdPerson && !isAnchoredPassenger) {
|
|
454
493
|
const yOffset = this.worldRenderer.playerStateReactive.eyeHeight
|
|
455
494
|
entity.position.set(this.worldRenderer.cameraWorldPos.x, this.worldRenderer.cameraWorldPos.y - yOffset, this.worldRenderer.cameraWorldPos.z)
|
|
456
495
|
}
|
|
@@ -462,6 +501,31 @@ export class Entities {
|
|
|
462
501
|
playerObject.animation.update(playerObject, dt)
|
|
463
502
|
}
|
|
464
503
|
|
|
504
|
+
if (!isPlayerEntity) {
|
|
505
|
+
const renderHints = entity.userData.renderHints as EntityRenderHints | undefined
|
|
506
|
+
if (shouldApplyLocalHorseCameraYawLock(renderHints)) {
|
|
507
|
+
applyLocalHorseCameraYawLock(entity, this.worldRenderer.cameraShake.getBaseRotation().yaw)
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const horseHeadRig = entity.userData[HORSE_HEAD_RIG_USER_DATA_KEY] as HorseHeadRig | undefined
|
|
512
|
+
const horseAnimation = entity.userData[HORSE_HEAD_ANIMATION_USER_DATA_KEY] as HorseHeadAnimationState | undefined
|
|
513
|
+
if (horseHeadRig && horseAnimation) {
|
|
514
|
+
const animation = updateHorseHeadAnimationFrame(horseAnimation, dt)
|
|
515
|
+
const pose = calculateHorseHeadPose({
|
|
516
|
+
entityPitch: entity.userData._horseHeadPitch ?? 0,
|
|
517
|
+
headYaw: entity.userData._horseHeadYaw ?? entity.userData._horseBodyYaw ?? 0,
|
|
518
|
+
bodyYaw: entity.userData._horseBodyYaw ?? 0,
|
|
519
|
+
limbSwing: animation.limbSwing,
|
|
520
|
+
limbSwingAmount: animation.limbSwingAmount
|
|
521
|
+
})
|
|
522
|
+
applyHorseHeadPose(horseHeadRig, pose)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (!isPlayerEntity && playerObject) {
|
|
526
|
+
applyNetworkHeadPitch(playerObject, entity.userData)
|
|
527
|
+
}
|
|
528
|
+
|
|
465
529
|
entity.traverse(child => {
|
|
466
530
|
if (child instanceof Entity.EntityMesh) {
|
|
467
531
|
child.update(dt)
|
|
@@ -482,21 +546,123 @@ export class Entities {
|
|
|
482
546
|
this.maybeRenderPlayerSkin(entityIdRaw)
|
|
483
547
|
}
|
|
484
548
|
|
|
485
|
-
if (entity.
|
|
549
|
+
if (entity.userData.renderHints?.localVehicle) {
|
|
550
|
+
const worldPos = this.resolveLocalVehicleRenderWorldPos(entity)
|
|
551
|
+
entity.position.set(worldPos.x, worldPos.y, worldPos.z)
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (entity.visible && !isPlayerEntity) {
|
|
486
555
|
this.syncArmorPositions(entity)
|
|
487
556
|
}
|
|
557
|
+
}
|
|
488
558
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
559
|
+
this.updateVehiclePassengerPositions()
|
|
560
|
+
this.applyRemoteBoatPassengerRotations()
|
|
561
|
+
this.applyLocalThirdPersonPlayerRotation()
|
|
562
|
+
this.updateBoatPaddleAnimations(dt)
|
|
563
|
+
}
|
|
492
564
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
565
|
+
private applyRemoteBoatPassengerRotations() {
|
|
566
|
+
processRemoteBoatPassengerRotations({
|
|
567
|
+
entities: this.entities as Record<string, RemoteBoatPassengerEntity>,
|
|
568
|
+
syncArmor: entity => this.syncArmorPositions(entity as SceneEntity)
|
|
569
|
+
})
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
private updateBoatPaddleAnimations(dt: number) {
|
|
573
|
+
for (const entity of Object.values(this.entities)) {
|
|
574
|
+
if (!entity) continue
|
|
575
|
+
if (!isBoatEntityName(entity['realName'] as string | undefined)) continue
|
|
576
|
+
|
|
577
|
+
const mesh = entity.children.find(child => child.name === 'mesh')
|
|
578
|
+
if (!mesh) continue
|
|
579
|
+
|
|
580
|
+
let animState = mesh.userData.boatPaddleAnimation as BoatPaddleAnimationState | undefined
|
|
581
|
+
if (!animState) continue
|
|
582
|
+
|
|
583
|
+
updateBoatPaddleAnimationState(
|
|
584
|
+
animState,
|
|
585
|
+
dt,
|
|
586
|
+
mesh.userData.boatPaddleLeftPivot as THREE.Object3D | undefined,
|
|
587
|
+
mesh.userData.boatPaddleRightPivot as THREE.Object3D | undefined,
|
|
588
|
+
this.boatPaddleScratch
|
|
589
|
+
)
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
private applyLocalThirdPersonPlayerRotation() {
|
|
594
|
+
const entity = this.playerEntity
|
|
595
|
+
if (!entity?.visible || !entity.playerObject) return
|
|
596
|
+
|
|
597
|
+
const rotation = this.worldRenderer.cameraShake.getBaseRotation()
|
|
598
|
+
const isAnchoredPassenger = entity.userData._passengerVehicleId != null || entity.userData._boatPassengerVehicleId != null
|
|
599
|
+
const anchoredVehicleId = entity.userData._passengerVehicleId ?? entity.userData._boatPassengerVehicleId
|
|
600
|
+
const vehicle = anchoredVehicleId != null ? this.entities[anchoredVehicleId] : undefined
|
|
601
|
+
const vehicleName = vehicle?.['realName'] ?? vehicle?.originalEntity.name
|
|
602
|
+
const vehicleYaw = vehicle?.rotation.y
|
|
603
|
+
|
|
604
|
+
if (
|
|
605
|
+
shouldApplyBoatPassengerThirdPersonRotation({
|
|
606
|
+
isThirdPerson: true,
|
|
607
|
+
isAnchoredPassenger,
|
|
608
|
+
vehicleName,
|
|
609
|
+
vehicleYaw
|
|
610
|
+
})
|
|
611
|
+
) {
|
|
612
|
+
const resolved = resolveBoatPassengerThirdPersonRotation({
|
|
613
|
+
cameraYaw: rotation.yaw,
|
|
614
|
+
cameraPitch: rotation.pitch,
|
|
615
|
+
vehicleYaw: vehicleYaw!
|
|
616
|
+
})
|
|
617
|
+
entity.rotation.set(0, resolved.bodyYaw, 0)
|
|
618
|
+
entity.traverse(child => {
|
|
619
|
+
if (child.name !== 'head') return
|
|
620
|
+
child.rotation.set(resolved.headPitch, resolved.headYaw, 0)
|
|
621
|
+
})
|
|
622
|
+
} else {
|
|
623
|
+
entity.rotation.set(0, rotation.yaw, 0)
|
|
624
|
+
entity.traverse(child => {
|
|
625
|
+
if (child.name !== 'head') return
|
|
626
|
+
child.rotation.set(-rotation.pitch, 0, 0)
|
|
627
|
+
})
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
this.syncArmorPositions(entity)
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
private resolvePassengerEntity(passengerId: number): SceneEntity | undefined {
|
|
634
|
+
if (this.playerEntity?.originalEntity.id === passengerId) {
|
|
635
|
+
return this.playerEntity
|
|
499
636
|
}
|
|
637
|
+
return this.entities[passengerId]
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
private resolveLocalVehicleRenderWorldPos(sceneEntity: SceneEntity): Vec3Like {
|
|
641
|
+
const originalEntity = sceneEntity.originalEntity
|
|
642
|
+
const renderHints = sceneEntity.userData.renderHints as EntityRenderHints | undefined
|
|
643
|
+
const rawVehicleY = sceneEntity.userData._localVehicleY ?? originalEntity.position?.y ?? sceneEntity.position.y
|
|
644
|
+
return resolveLocalVehicleWorldPosition({
|
|
645
|
+
cameraWorldPos: this.worldRenderer.cameraWorldPos,
|
|
646
|
+
rawVehicleY,
|
|
647
|
+
eyeHeight: this.worldRenderer.playerStateReactive.eyeHeight,
|
|
648
|
+
vehicleName: sceneEntity['realName'] ?? originalEntity.name,
|
|
649
|
+
vehicleHeight: originalEntity.height ?? 1.6,
|
|
650
|
+
verticalCameraLock: renderHints?.localVehicleVerticalCameraLock
|
|
651
|
+
})
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
updateVehiclePassengerPositions() {
|
|
655
|
+
applyVehiclePassengerPositions({
|
|
656
|
+
entities: this.entities,
|
|
657
|
+
localPlayer: this.playerEntity,
|
|
658
|
+
getWorldPosition: target => this.worldRenderer.sceneOrigin.getWorldPosition(target as SceneEntity),
|
|
659
|
+
version: this.worldRenderer.version
|
|
660
|
+
})
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/** @deprecated Use updateVehiclePassengerPositions */
|
|
664
|
+
updateBoatPassengerPositions() {
|
|
665
|
+
this.updateVehiclePassengerPositions()
|
|
500
666
|
}
|
|
501
667
|
|
|
502
668
|
private syncArmorPositions(entity: SceneEntity) {
|
|
@@ -763,7 +929,10 @@ export class Entities {
|
|
|
763
929
|
}
|
|
764
930
|
}
|
|
765
931
|
|
|
766
|
-
private applyMovementAnimation(
|
|
932
|
+
private applyMovementAnimation(
|
|
933
|
+
playerObject: PlayerObjectType,
|
|
934
|
+
animation: 'walking' | 'running' | 'oneSwing' | 'idle' | 'crouch' | 'crouchWalking' | 'riding'
|
|
935
|
+
): void {
|
|
767
936
|
const anim = playerObject.animation as WalkingGeneralSwing | undefined
|
|
768
937
|
if (!anim) return
|
|
769
938
|
|
|
@@ -773,12 +942,21 @@ export class Entities {
|
|
|
773
942
|
}
|
|
774
943
|
|
|
775
944
|
anim.switchAnimationCallback = null
|
|
945
|
+
if (animation === 'riding') {
|
|
946
|
+
anim.isMoving = false
|
|
947
|
+
anim.isRunning = false
|
|
948
|
+
anim.isCrouched = false
|
|
949
|
+
anim.isRiding = true
|
|
950
|
+
return
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
anim.isRiding = false
|
|
776
954
|
anim.isMoving = animation === 'walking' || animation === 'running' || animation === 'crouchWalking'
|
|
777
955
|
anim.isRunning = animation === 'running'
|
|
778
956
|
anim.isCrouched = animation === 'crouch' || animation === 'crouchWalking'
|
|
779
957
|
}
|
|
780
958
|
|
|
781
|
-
playAnimation(entityPlayerId, animation: 'walking' | 'running' | 'oneSwing' | 'idle' | 'crouch' | 'crouchWalking') {
|
|
959
|
+
playAnimation(entityPlayerId, animation: 'walking' | 'running' | 'oneSwing' | 'idle' | 'crouch' | 'crouchWalking' | 'riding') {
|
|
782
960
|
const playerObject =
|
|
783
961
|
entityPlayerId === 'player_entity' ? this.playerEntity?.playerObject : (this.getPlayerObject(entityPlayerId) ?? this.playerEntity?.playerObject)
|
|
784
962
|
|
|
@@ -898,6 +1076,10 @@ export class Entities {
|
|
|
898
1076
|
if (!e) return
|
|
899
1077
|
e.userData._posTween?.stop()
|
|
900
1078
|
e.userData._rotTween?.stop()
|
|
1079
|
+
e.userData[HORSE_HEAD_ANIMATION_USER_DATA_KEY] = undefined
|
|
1080
|
+
e.userData[HORSE_HEAD_RIG_USER_DATA_KEY] = undefined
|
|
1081
|
+
const boatMesh = e.children.find(c => c.name === 'mesh')
|
|
1082
|
+
if (boatMesh) disposeBoatWaterPatch(boatMesh)
|
|
901
1083
|
if (e.additionalCleanup) e.additionalCleanup()
|
|
902
1084
|
e.traverse(c => {
|
|
903
1085
|
if (c['additionalCleanup']) c['additionalCleanup']()
|
|
@@ -989,6 +1171,11 @@ export class Entities {
|
|
|
989
1171
|
}
|
|
990
1172
|
if (!mesh) return
|
|
991
1173
|
mesh.name = 'mesh'
|
|
1174
|
+
const boatMeshYawOffset = getBoatMeshYawOffset(entity.name)
|
|
1175
|
+
if (boatMeshYawOffset != null) {
|
|
1176
|
+
mesh.rotation.y = boatMeshYawOffset
|
|
1177
|
+
setupBoatMesh(mesh)
|
|
1178
|
+
}
|
|
992
1179
|
// set initial position so there are no weird jumps update after
|
|
993
1180
|
const pos = entity.pos ?? entity.position
|
|
994
1181
|
this.worldRenderer.sceneOrigin.track(group)
|
|
@@ -1016,10 +1203,20 @@ export class Entities {
|
|
|
1016
1203
|
this.syncSceneAttachment(group)
|
|
1017
1204
|
|
|
1018
1205
|
this.afterAddEntity(entity)
|
|
1206
|
+
const horseHeadRig = mesh.userData[HORSE_HEAD_RIG_USER_DATA_KEY] as HorseHeadRig | undefined
|
|
1207
|
+
if (horseHeadRig) {
|
|
1208
|
+
e.userData[HORSE_HEAD_RIG_USER_DATA_KEY] = horseHeadRig
|
|
1209
|
+
e.userData[HORSE_HEAD_ANIMATION_USER_DATA_KEY] = createHorseHeadAnimationState()
|
|
1210
|
+
}
|
|
1019
1211
|
} else {
|
|
1020
1212
|
mesh = e.children.find(c => c.name === 'mesh')
|
|
1213
|
+
if (entity.renderHints) {
|
|
1214
|
+
e.originalEntity.renderHints = entity.renderHints
|
|
1215
|
+
}
|
|
1021
1216
|
}
|
|
1022
1217
|
|
|
1218
|
+
this.applyEntityRenderHints(e, entity)
|
|
1219
|
+
|
|
1023
1220
|
// Update equipment
|
|
1024
1221
|
this.updateEntityEquipment(e, entity)
|
|
1025
1222
|
|
|
@@ -1211,26 +1408,88 @@ export class Entities {
|
|
|
1211
1408
|
this.updateEntityPosition(entity, justAdded, overrides)
|
|
1212
1409
|
}
|
|
1213
1410
|
|
|
1214
|
-
|
|
1411
|
+
applyEntityRenderHints(e: SceneEntity, entity: SceneEntity['originalEntity']) {
|
|
1412
|
+
if (entity.renderHints) {
|
|
1413
|
+
const hints = entity.renderHints as EntityRenderHints
|
|
1414
|
+
const nextPassengerIds = new Set(hints.passengerIds ?? hints.boatPassengerIds ?? [])
|
|
1415
|
+
const previousPassengerIds = e.userData.renderHints?.passengerIds ?? e.userData.renderHints?.boatPassengerIds ?? []
|
|
1416
|
+
const isPassengerVehicle = isBoatEntityName(entity.name) || isRideableMinecartEntityName(entity.name)
|
|
1417
|
+
if (isPassengerVehicle) {
|
|
1418
|
+
for (const previousPassengerId of previousPassengerIds) {
|
|
1419
|
+
if (nextPassengerIds.has(previousPassengerId)) continue
|
|
1420
|
+
const previousPassenger = this.resolvePassengerEntity(previousPassengerId)
|
|
1421
|
+
if (previousPassenger) {
|
|
1422
|
+
releaseVehiclePassengerPosition(previousPassenger, String(entity.id), this.worldRenderer.sceneOrigin.getWorldPosition(previousPassenger))
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
e.userData.renderHints = entity.renderHints
|
|
1427
|
+
}
|
|
1428
|
+
if (!isBoatEntityName(entity.name)) return
|
|
1429
|
+
const mesh = e.children.find(c => c.name === 'mesh')
|
|
1430
|
+
if (!mesh) return
|
|
1431
|
+
const waterPatch = mesh.userData.boatWaterPatch as THREE.Object3D | undefined
|
|
1432
|
+
if (waterPatch) {
|
|
1433
|
+
waterPatch.visible = entity.renderHints?.boatWaterPatchVisible === true
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
const hints = (e.userData.renderHints ?? entity.renderHints) as EntityRenderHints | undefined
|
|
1437
|
+
const leftActive = hints?.boatPaddleLeft === true
|
|
1438
|
+
const rightActive = hints?.boatPaddleRight === true
|
|
1439
|
+
let animState = mesh.userData.boatPaddleAnimation as BoatPaddleAnimationState | undefined
|
|
1440
|
+
if (!animState) {
|
|
1441
|
+
animState = createBoatPaddleAnimationState()
|
|
1442
|
+
mesh.userData.boatPaddleAnimation = animState
|
|
1443
|
+
}
|
|
1444
|
+
syncBoatPaddleAnimationTargets(animState, leftActive, rightActive)
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
updateEntityPosition(
|
|
1448
|
+
entity: SceneEntity['originalEntity'],
|
|
1449
|
+
justAdded: boolean,
|
|
1450
|
+
overrides: { rotation?: { head?: { y: number; x: number } } },
|
|
1451
|
+
headRotationOnly = false
|
|
1452
|
+
) {
|
|
1215
1453
|
const e = this.entities[entity.id]
|
|
1216
1454
|
if (!e) return
|
|
1217
|
-
|
|
1455
|
+
this.applyEntityRenderHints(e, entity)
|
|
1456
|
+
if (e.userData[HORSE_HEAD_RIG_USER_DATA_KEY]) {
|
|
1457
|
+
e.userData._horseHeadPitch = typeof entity.pitch === 'number' && Number.isFinite(entity.pitch) ? entity.pitch : 0
|
|
1458
|
+
const headYaw = (entity as any).headYaw
|
|
1459
|
+
e.userData._horseHeadYaw = typeof headYaw === 'number' && Number.isFinite(headYaw) ? headYaw : entity.yaw
|
|
1460
|
+
e.userData._horseBodyYaw = typeof entity.yaw === 'number' && Number.isFinite(entity.yaw) ? entity.yaw : 0
|
|
1461
|
+
}
|
|
1462
|
+
const cameraSynced = usesCameraSyncedVehiclePosition(entity)
|
|
1218
1463
|
if (entity.position) {
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
e.userData.
|
|
1464
|
+
if (cameraSynced) {
|
|
1465
|
+
e.userData._localVehicleY = entity.position.y
|
|
1466
|
+
e.userData._posTween?.stop()
|
|
1467
|
+
e.userData._posTween = undefined
|
|
1468
|
+
const worldPos = this.resolveLocalVehicleRenderWorldPos(e)
|
|
1469
|
+
e.position.set(worldPos.x, worldPos.y, worldPos.z)
|
|
1470
|
+
} else {
|
|
1471
|
+
const ANIMATION_DURATION = getEntityTweenDurationMs(entity, justAdded)
|
|
1472
|
+
// Initialize tween target from current world position
|
|
1473
|
+
const currentWorld = this.worldRenderer.sceneOrigin.getWorldPosition(e) ?? { x: entity.position.x, y: entity.position.y, z: entity.position.z }
|
|
1474
|
+
if (!e.userData._tweenTarget) {
|
|
1475
|
+
e.userData._tweenTarget = { x: currentWorld.x, y: currentWorld.y, z: currentWorld.z }
|
|
1476
|
+
}
|
|
1477
|
+
// Stop previous position tween to prevent accumulation
|
|
1478
|
+
e.userData._posTween?.stop()
|
|
1479
|
+
// Tween a separate target object, apply via proxy on each update
|
|
1480
|
+
e.userData._posTween = new TWEEN.Tween(e.userData._tweenTarget)
|
|
1481
|
+
.to({ x: entity.position.x, y: entity.position.y, z: entity.position.z }, ANIMATION_DURATION)
|
|
1482
|
+
.onUpdate(() => {
|
|
1483
|
+
e.position.set(e.userData._tweenTarget.x, e.userData._tweenTarget.y, e.userData._tweenTarget.z)
|
|
1484
|
+
})
|
|
1485
|
+
.start()
|
|
1486
|
+
}
|
|
1487
|
+
const horseAnimation = e.userData[HORSE_HEAD_ANIMATION_USER_DATA_KEY] as HorseHeadAnimationState | undefined
|
|
1488
|
+
if (!headRotationOnly && horseAnimation && Number.isFinite(entity.position.x) && Number.isFinite(entity.position.z)) {
|
|
1489
|
+
setHorseHeadAnimationPosition(horseAnimation, entity.position)
|
|
1223
1490
|
}
|
|
1224
|
-
// Stop previous position tween to prevent accumulation
|
|
1225
|
-
e.userData._posTween?.stop()
|
|
1226
|
-
// Tween a separate target object, apply via proxy on each update
|
|
1227
|
-
e.userData._posTween = new TWEEN.Tween(e.userData._tweenTarget)
|
|
1228
|
-
.to({ x: entity.position.x, y: entity.position.y, z: entity.position.z }, ANIMATION_DURATION)
|
|
1229
|
-
.onUpdate(() => {
|
|
1230
|
-
e.position.set(e.userData._tweenTarget.x, e.userData._tweenTarget.y, e.userData._tweenTarget.z)
|
|
1231
|
-
})
|
|
1232
|
-
.start()
|
|
1233
1491
|
}
|
|
1492
|
+
const rotationTweenDuration = getEntityRotationTweenDurationMs(entity, justAdded)
|
|
1234
1493
|
/** World yaw for the whole model: for PlayerObject skins, rotate body to head look dir; head mesh stays yaw-fixed (pitch only). */
|
|
1235
1494
|
let targetYaw: number | undefined
|
|
1236
1495
|
if (e.playerObject && overrides?.rotation?.head) {
|
|
@@ -1243,18 +1502,29 @@ export class Entities {
|
|
|
1243
1502
|
targetYaw = entity.yaw
|
|
1244
1503
|
}
|
|
1245
1504
|
if (typeof targetYaw === 'number' && Number.isFinite(targetYaw)) {
|
|
1246
|
-
const dy = shortestYawRadians(e.rotation.y, targetYaw)
|
|
1247
1505
|
// Stop previous rotation tween to prevent accumulation (mirror _posTween)
|
|
1248
1506
|
e.userData._rotTween?.stop()
|
|
1249
|
-
e.userData._rotTween =
|
|
1507
|
+
e.userData._rotTween = undefined
|
|
1508
|
+
const currentYaw = Number.isFinite(e.rotation.y) ? e.rotation.y : 0
|
|
1509
|
+
const dy = shortestYawRadians(currentYaw, targetYaw)
|
|
1510
|
+
if (rotationTweenDuration === 0) {
|
|
1511
|
+
e.rotation.y = currentYaw + dy
|
|
1512
|
+
} else {
|
|
1513
|
+
e.userData._rotTween = new TWEEN.Tween(e.rotation).to({ y: currentYaw + dy }, rotationTweenDuration).start()
|
|
1514
|
+
}
|
|
1250
1515
|
}
|
|
1251
1516
|
|
|
1252
1517
|
if (e?.playerObject && overrides?.rotation?.head) {
|
|
1253
1518
|
const { playerObject } = e
|
|
1519
|
+
const hy = overrides.rotation.head.y
|
|
1520
|
+
storeNetworkHeadYaw(e.userData, hy, entity.yaw)
|
|
1254
1521
|
playerObject.skin.head.rotation.y = 0
|
|
1255
1522
|
|
|
1256
1523
|
const hp = overrides.rotation.head.x
|
|
1257
|
-
|
|
1524
|
+
storeNetworkHeadPitch(e.userData, hp)
|
|
1525
|
+
applyNetworkHeadPitch(playerObject, e.userData)
|
|
1526
|
+
} else if (e?.playerObject && typeof entity.yaw === 'number' && Number.isFinite(entity.yaw)) {
|
|
1527
|
+
storeNetworkHeadYaw(e.userData, undefined, entity.yaw)
|
|
1258
1528
|
}
|
|
1259
1529
|
}
|
|
1260
1530
|
|
|
@@ -14,6 +14,9 @@ import entities from './entities.json'
|
|
|
14
14
|
import { externalModels } from './objModels'
|
|
15
15
|
import externalTexturesJson from './externalTextures.json'
|
|
16
16
|
import { createAnimatedObject } from './gltfAnimationUtils'
|
|
17
|
+
import { BOAT_OBJ_OFFSET_Y } from './boatRenderSetup'
|
|
18
|
+
import { VANILLA_117_HORSE_ENTITY_SCALES } from './horseEntityScale'
|
|
19
|
+
import { createHorseHeadRig } from './horseHeadAnimation'
|
|
17
20
|
|
|
18
21
|
interface ElemFace {
|
|
19
22
|
dir: [number, number, number]
|
|
@@ -441,6 +444,7 @@ const getEntity = (name: string) => {
|
|
|
441
444
|
}
|
|
442
445
|
|
|
443
446
|
const scaleEntity: Record<string, number> = {
|
|
447
|
+
...VANILLA_117_HORSE_ENTITY_SCALES,
|
|
444
448
|
zombie: 1.85,
|
|
445
449
|
husk: 1.85,
|
|
446
450
|
arrow: 0.0025
|
|
@@ -449,7 +453,7 @@ const scaleEntity: Record<string, number> = {
|
|
|
449
453
|
const offsetEntity: Record<string, Vec3> = {
|
|
450
454
|
zombie: new Vec3(0, 1, 0),
|
|
451
455
|
husk: new Vec3(0, 1, 0),
|
|
452
|
-
boat: new Vec3(0,
|
|
456
|
+
boat: new Vec3(0, BOAT_OBJ_OFFSET_Y, 0),
|
|
453
457
|
arrow: new Vec3(0, -0.9, 0)
|
|
454
458
|
}
|
|
455
459
|
|
|
@@ -627,14 +631,14 @@ export class EntityMesh {
|
|
|
627
631
|
const obj = objLoader.parse(externalModels[type])
|
|
628
632
|
const scale = scaleEntity[originalType] || scaleEntity[type]
|
|
629
633
|
if (scale) obj.scale.set(scale, scale, scale)
|
|
630
|
-
const offset = offsetEntity[originalType]
|
|
634
|
+
const offset = offsetEntity[originalType] ?? offsetEntity[type]
|
|
631
635
|
if (offset) obj.position.set(offset.x, offset.y, offset.z)
|
|
632
636
|
obj.traverse(child => {
|
|
633
637
|
if (child instanceof THREE.Mesh) {
|
|
634
638
|
child.material = material
|
|
635
639
|
// todo
|
|
636
640
|
if (child.name === 'Head layer') child.visible = false
|
|
637
|
-
if (child.name === 'Head' && overrides.rotation?.head) {
|
|
641
|
+
if (type !== 'horse' && child.name === 'Head' && overrides.rotation?.head) {
|
|
638
642
|
// todo
|
|
639
643
|
child.rotation.x -= ((overrides.rotation.head.x ?? 0) * Math.PI) / 180
|
|
640
644
|
child.rotation.y -= ((overrides.rotation.head.y ?? 0) * Math.PI) / 180
|
|
@@ -642,6 +646,7 @@ export class EntityMesh {
|
|
|
642
646
|
}
|
|
643
647
|
}
|
|
644
648
|
})
|
|
649
|
+
if (type === 'horse') createHorseHeadRig(obj)
|
|
645
650
|
this.mesh = obj
|
|
646
651
|
debugFlags.type = 'obj'
|
|
647
652
|
return
|
|
@@ -32,6 +32,7 @@ export class WalkingGeneralSwing extends PlayerAnimation {
|
|
|
32
32
|
isRunning = false
|
|
33
33
|
isMoving = true
|
|
34
34
|
isCrouched = false
|
|
35
|
+
isRiding = false
|
|
35
36
|
|
|
36
37
|
_dt = 0
|
|
37
38
|
_phase = 0
|
|
@@ -127,7 +128,7 @@ export class WalkingGeneralSwing extends PlayerAnimation {
|
|
|
127
128
|
if (!this._defaults) this._captureDefaults(player)
|
|
128
129
|
this._applyDefaults(player)
|
|
129
130
|
|
|
130
|
-
const targetMove = this.isMoving ? 1 : 0
|
|
131
|
+
const targetMove = this.isRiding ? 0 : this.isMoving ? 1 : 0
|
|
131
132
|
const kMove = Math.min(1, dt * 20)
|
|
132
133
|
this._moveBlend += (targetMove - this._moveBlend) * kMove
|
|
133
134
|
|
|
@@ -139,35 +140,46 @@ export class WalkingGeneralSwing extends PlayerAnimation {
|
|
|
139
140
|
|
|
140
141
|
applyCrouchPose(player, this.isCrouched ? 1 : 0)
|
|
141
142
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
reset = true
|
|
146
|
-
}
|
|
147
|
-
}
|
|
143
|
+
if (this.isRiding) {
|
|
144
|
+
player.skin.rightArm.rotation.x -= Math.PI / 5
|
|
145
|
+
player.skin.leftArm.rotation.x -= Math.PI / 5
|
|
148
146
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
147
|
+
// Vanilla HumanoidModel uses ModelPart Z→Y→X order with yRot/zRot signs as in Java.
|
|
148
|
+
// skinview3d Y/Z axes are opposite the Java model, so ZYX plus inverted Y/Z signs
|
|
149
|
+
// matches vanilla Rz × Ry × Rx (see BOAT_RIDING_LEG_SPREAD.md).
|
|
150
|
+
player.skin.rightLeg.rotation.set(-1.4137167, -Math.PI / 10, -Math.PI / 40, 'ZYX')
|
|
151
|
+
player.skin.leftLeg.rotation.set(-1.4137167, Math.PI / 10, Math.PI / 40, 'ZYX')
|
|
152
152
|
} else {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
const boundary = this.isRunning ? Math.cos(t) : Math.sin(t)
|
|
154
|
+
if (Math.abs(boundary) < 0.02) {
|
|
155
|
+
if (this.switchAnimationCallback) {
|
|
156
|
+
reset = true
|
|
157
|
+
}
|
|
158
|
+
}
|
|
156
159
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
+
if (this.isRunning) {
|
|
161
|
+
player.skin.leftLeg.rotation.x += Math.cos(t + Math.PI) * 1.3 * this._moveBlend
|
|
162
|
+
player.skin.rightLeg.rotation.x += Math.cos(t) * 1.3 * this._moveBlend
|
|
163
|
+
} else {
|
|
164
|
+
player.skin.leftLeg.rotation.x += Math.sin(t) * 0.5 * this._moveBlend
|
|
165
|
+
player.skin.rightLeg.rotation.x += Math.sin(t + Math.PI) * 0.5 * this._moveBlend
|
|
166
|
+
}
|
|
160
167
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
} else {
|
|
165
|
-
player.skin.leftArm.rotation.x += Math.sin(t + Math.PI) * 0.5 * this._moveBlend
|
|
166
|
-
player.skin.rightArm.rotation.x += Math.sin(t) * 0.5 * this._moveBlend
|
|
168
|
+
if (this.isRunning) {
|
|
169
|
+
player.skin.leftArm.rotation.x += Math.cos(t) * 1.5 * this._moveBlend
|
|
170
|
+
player.skin.rightArm.rotation.x += Math.cos(t + Math.PI) * 1.5 * this._moveBlend
|
|
167
171
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
172
|
+
const basicArmRotationZ = Math.PI * 0.1
|
|
173
|
+
player.skin.leftArm.rotation.z += (Math.cos(t) * 0.1 + basicArmRotationZ) * this._moveBlend
|
|
174
|
+
player.skin.rightArm.rotation.z += (Math.cos(t + Math.PI) * 0.1 - basicArmRotationZ) * this._moveBlend
|
|
175
|
+
} else {
|
|
176
|
+
player.skin.leftArm.rotation.x += Math.sin(t + Math.PI) * 0.5 * this._moveBlend
|
|
177
|
+
player.skin.rightArm.rotation.x += Math.sin(t) * 0.5 * this._moveBlend
|
|
178
|
+
|
|
179
|
+
const basicArmRotationZ = Math.PI * 0.02
|
|
180
|
+
player.skin.leftArm.rotation.z += (Math.cos(t) * 0.03 + basicArmRotationZ) * this._moveBlend
|
|
181
|
+
player.skin.rightArm.rotation.z += (Math.cos(t + Math.PI) * 0.03 - basicArmRotationZ) * this._moveBlend
|
|
182
|
+
}
|
|
171
183
|
}
|
|
172
184
|
|
|
173
185
|
if (this._swingTime !== null) {
|