minecraft-renderer 0.1.93 → 0.1.94
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 +302 -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 +259 -0
- package/src/three/entity/interpolationPolicy.ts +187 -0
- package/src/three/entity/localHorseCameraYawLock.test.ts +72 -0
- package/src/three/entity/localVehicleRendering.integration.test.ts +247 -0
- package/src/three/entity/minecartLocalPassengerRendering.test.ts +208 -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 +227 -0
- package/src/three/entity/remoteBoatPassengerRotation.ts +128 -0
- package/src/three/entity/vehiclePassengerProduction.test.ts +214 -0
- package/src/three/entity/vehiclePassengerRendering.ts +56 -0
- package/src/three/entity/vehiclePassengerUpdate.ts +82 -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,122 @@ 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
|
+
})
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/** @deprecated Use updateVehiclePassengerPositions */
|
|
663
|
+
updateBoatPassengerPositions() {
|
|
664
|
+
this.updateVehiclePassengerPositions()
|
|
500
665
|
}
|
|
501
666
|
|
|
502
667
|
private syncArmorPositions(entity: SceneEntity) {
|
|
@@ -763,7 +928,10 @@ export class Entities {
|
|
|
763
928
|
}
|
|
764
929
|
}
|
|
765
930
|
|
|
766
|
-
private applyMovementAnimation(
|
|
931
|
+
private applyMovementAnimation(
|
|
932
|
+
playerObject: PlayerObjectType,
|
|
933
|
+
animation: 'walking' | 'running' | 'oneSwing' | 'idle' | 'crouch' | 'crouchWalking' | 'riding'
|
|
934
|
+
): void {
|
|
767
935
|
const anim = playerObject.animation as WalkingGeneralSwing | undefined
|
|
768
936
|
if (!anim) return
|
|
769
937
|
|
|
@@ -773,12 +941,21 @@ export class Entities {
|
|
|
773
941
|
}
|
|
774
942
|
|
|
775
943
|
anim.switchAnimationCallback = null
|
|
944
|
+
if (animation === 'riding') {
|
|
945
|
+
anim.isMoving = false
|
|
946
|
+
anim.isRunning = false
|
|
947
|
+
anim.isCrouched = false
|
|
948
|
+
anim.isRiding = true
|
|
949
|
+
return
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
anim.isRiding = false
|
|
776
953
|
anim.isMoving = animation === 'walking' || animation === 'running' || animation === 'crouchWalking'
|
|
777
954
|
anim.isRunning = animation === 'running'
|
|
778
955
|
anim.isCrouched = animation === 'crouch' || animation === 'crouchWalking'
|
|
779
956
|
}
|
|
780
957
|
|
|
781
|
-
playAnimation(entityPlayerId, animation: 'walking' | 'running' | 'oneSwing' | 'idle' | 'crouch' | 'crouchWalking') {
|
|
958
|
+
playAnimation(entityPlayerId, animation: 'walking' | 'running' | 'oneSwing' | 'idle' | 'crouch' | 'crouchWalking' | 'riding') {
|
|
782
959
|
const playerObject =
|
|
783
960
|
entityPlayerId === 'player_entity' ? this.playerEntity?.playerObject : (this.getPlayerObject(entityPlayerId) ?? this.playerEntity?.playerObject)
|
|
784
961
|
|
|
@@ -898,6 +1075,10 @@ export class Entities {
|
|
|
898
1075
|
if (!e) return
|
|
899
1076
|
e.userData._posTween?.stop()
|
|
900
1077
|
e.userData._rotTween?.stop()
|
|
1078
|
+
e.userData[HORSE_HEAD_ANIMATION_USER_DATA_KEY] = undefined
|
|
1079
|
+
e.userData[HORSE_HEAD_RIG_USER_DATA_KEY] = undefined
|
|
1080
|
+
const boatMesh = e.children.find(c => c.name === 'mesh')
|
|
1081
|
+
if (boatMesh) disposeBoatWaterPatch(boatMesh)
|
|
901
1082
|
if (e.additionalCleanup) e.additionalCleanup()
|
|
902
1083
|
e.traverse(c => {
|
|
903
1084
|
if (c['additionalCleanup']) c['additionalCleanup']()
|
|
@@ -989,6 +1170,11 @@ export class Entities {
|
|
|
989
1170
|
}
|
|
990
1171
|
if (!mesh) return
|
|
991
1172
|
mesh.name = 'mesh'
|
|
1173
|
+
const boatMeshYawOffset = getBoatMeshYawOffset(entity.name)
|
|
1174
|
+
if (boatMeshYawOffset != null) {
|
|
1175
|
+
mesh.rotation.y = boatMeshYawOffset
|
|
1176
|
+
setupBoatMesh(mesh)
|
|
1177
|
+
}
|
|
992
1178
|
// set initial position so there are no weird jumps update after
|
|
993
1179
|
const pos = entity.pos ?? entity.position
|
|
994
1180
|
this.worldRenderer.sceneOrigin.track(group)
|
|
@@ -1016,10 +1202,20 @@ export class Entities {
|
|
|
1016
1202
|
this.syncSceneAttachment(group)
|
|
1017
1203
|
|
|
1018
1204
|
this.afterAddEntity(entity)
|
|
1205
|
+
const horseHeadRig = mesh.userData[HORSE_HEAD_RIG_USER_DATA_KEY] as HorseHeadRig | undefined
|
|
1206
|
+
if (horseHeadRig) {
|
|
1207
|
+
e.userData[HORSE_HEAD_RIG_USER_DATA_KEY] = horseHeadRig
|
|
1208
|
+
e.userData[HORSE_HEAD_ANIMATION_USER_DATA_KEY] = createHorseHeadAnimationState()
|
|
1209
|
+
}
|
|
1019
1210
|
} else {
|
|
1020
1211
|
mesh = e.children.find(c => c.name === 'mesh')
|
|
1212
|
+
if (entity.renderHints) {
|
|
1213
|
+
e.originalEntity.renderHints = entity.renderHints
|
|
1214
|
+
}
|
|
1021
1215
|
}
|
|
1022
1216
|
|
|
1217
|
+
this.applyEntityRenderHints(e, entity)
|
|
1218
|
+
|
|
1023
1219
|
// Update equipment
|
|
1024
1220
|
this.updateEntityEquipment(e, entity)
|
|
1025
1221
|
|
|
@@ -1211,26 +1407,88 @@ export class Entities {
|
|
|
1211
1407
|
this.updateEntityPosition(entity, justAdded, overrides)
|
|
1212
1408
|
}
|
|
1213
1409
|
|
|
1214
|
-
|
|
1410
|
+
applyEntityRenderHints(e: SceneEntity, entity: SceneEntity['originalEntity']) {
|
|
1411
|
+
if (entity.renderHints) {
|
|
1412
|
+
const hints = entity.renderHints as EntityRenderHints
|
|
1413
|
+
const nextPassengerIds = new Set(hints.passengerIds ?? hints.boatPassengerIds ?? [])
|
|
1414
|
+
const previousPassengerIds = e.userData.renderHints?.passengerIds ?? e.userData.renderHints?.boatPassengerIds ?? []
|
|
1415
|
+
const isPassengerVehicle = isBoatEntityName(entity.name) || isRideableMinecartEntityName(entity.name)
|
|
1416
|
+
if (isPassengerVehicle) {
|
|
1417
|
+
for (const previousPassengerId of previousPassengerIds) {
|
|
1418
|
+
if (nextPassengerIds.has(previousPassengerId)) continue
|
|
1419
|
+
const previousPassenger = this.resolvePassengerEntity(previousPassengerId)
|
|
1420
|
+
if (previousPassenger) {
|
|
1421
|
+
releaseVehiclePassengerPosition(previousPassenger, String(entity.id), this.worldRenderer.sceneOrigin.getWorldPosition(previousPassenger))
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
e.userData.renderHints = entity.renderHints
|
|
1426
|
+
}
|
|
1427
|
+
if (!isBoatEntityName(entity.name)) return
|
|
1428
|
+
const mesh = e.children.find(c => c.name === 'mesh')
|
|
1429
|
+
if (!mesh) return
|
|
1430
|
+
const waterPatch = mesh.userData.boatWaterPatch as THREE.Object3D | undefined
|
|
1431
|
+
if (waterPatch) {
|
|
1432
|
+
waterPatch.visible = entity.renderHints?.boatWaterPatchVisible === true
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
const hints = (e.userData.renderHints ?? entity.renderHints) as EntityRenderHints | undefined
|
|
1436
|
+
const leftActive = hints?.boatPaddleLeft === true
|
|
1437
|
+
const rightActive = hints?.boatPaddleRight === true
|
|
1438
|
+
let animState = mesh.userData.boatPaddleAnimation as BoatPaddleAnimationState | undefined
|
|
1439
|
+
if (!animState) {
|
|
1440
|
+
animState = createBoatPaddleAnimationState()
|
|
1441
|
+
mesh.userData.boatPaddleAnimation = animState
|
|
1442
|
+
}
|
|
1443
|
+
syncBoatPaddleAnimationTargets(animState, leftActive, rightActive)
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
updateEntityPosition(
|
|
1447
|
+
entity: SceneEntity['originalEntity'],
|
|
1448
|
+
justAdded: boolean,
|
|
1449
|
+
overrides: { rotation?: { head?: { y: number; x: number } } },
|
|
1450
|
+
headRotationOnly = false
|
|
1451
|
+
) {
|
|
1215
1452
|
const e = this.entities[entity.id]
|
|
1216
1453
|
if (!e) return
|
|
1217
|
-
|
|
1454
|
+
this.applyEntityRenderHints(e, entity)
|
|
1455
|
+
if (e.userData[HORSE_HEAD_RIG_USER_DATA_KEY]) {
|
|
1456
|
+
e.userData._horseHeadPitch = typeof entity.pitch === 'number' && Number.isFinite(entity.pitch) ? entity.pitch : 0
|
|
1457
|
+
const headYaw = (entity as any).headYaw
|
|
1458
|
+
e.userData._horseHeadYaw = typeof headYaw === 'number' && Number.isFinite(headYaw) ? headYaw : entity.yaw
|
|
1459
|
+
e.userData._horseBodyYaw = typeof entity.yaw === 'number' && Number.isFinite(entity.yaw) ? entity.yaw : 0
|
|
1460
|
+
}
|
|
1461
|
+
const cameraSynced = usesCameraSyncedVehiclePosition(entity)
|
|
1218
1462
|
if (entity.position) {
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
e.userData.
|
|
1463
|
+
if (cameraSynced) {
|
|
1464
|
+
e.userData._localVehicleY = entity.position.y
|
|
1465
|
+
e.userData._posTween?.stop()
|
|
1466
|
+
e.userData._posTween = undefined
|
|
1467
|
+
const worldPos = this.resolveLocalVehicleRenderWorldPos(e)
|
|
1468
|
+
e.position.set(worldPos.x, worldPos.y, worldPos.z)
|
|
1469
|
+
} else {
|
|
1470
|
+
const ANIMATION_DURATION = getEntityTweenDurationMs(entity, justAdded)
|
|
1471
|
+
// Initialize tween target from current world position
|
|
1472
|
+
const currentWorld = this.worldRenderer.sceneOrigin.getWorldPosition(e) ?? { x: entity.position.x, y: entity.position.y, z: entity.position.z }
|
|
1473
|
+
if (!e.userData._tweenTarget) {
|
|
1474
|
+
e.userData._tweenTarget = { x: currentWorld.x, y: currentWorld.y, z: currentWorld.z }
|
|
1475
|
+
}
|
|
1476
|
+
// Stop previous position tween to prevent accumulation
|
|
1477
|
+
e.userData._posTween?.stop()
|
|
1478
|
+
// Tween a separate target object, apply via proxy on each update
|
|
1479
|
+
e.userData._posTween = new TWEEN.Tween(e.userData._tweenTarget)
|
|
1480
|
+
.to({ x: entity.position.x, y: entity.position.y, z: entity.position.z }, ANIMATION_DURATION)
|
|
1481
|
+
.onUpdate(() => {
|
|
1482
|
+
e.position.set(e.userData._tweenTarget.x, e.userData._tweenTarget.y, e.userData._tweenTarget.z)
|
|
1483
|
+
})
|
|
1484
|
+
.start()
|
|
1485
|
+
}
|
|
1486
|
+
const horseAnimation = e.userData[HORSE_HEAD_ANIMATION_USER_DATA_KEY] as HorseHeadAnimationState | undefined
|
|
1487
|
+
if (!headRotationOnly && horseAnimation && Number.isFinite(entity.position.x) && Number.isFinite(entity.position.z)) {
|
|
1488
|
+
setHorseHeadAnimationPosition(horseAnimation, entity.position)
|
|
1223
1489
|
}
|
|
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
1490
|
}
|
|
1491
|
+
const rotationTweenDuration = getEntityRotationTweenDurationMs(entity, justAdded)
|
|
1234
1492
|
/** World yaw for the whole model: for PlayerObject skins, rotate body to head look dir; head mesh stays yaw-fixed (pitch only). */
|
|
1235
1493
|
let targetYaw: number | undefined
|
|
1236
1494
|
if (e.playerObject && overrides?.rotation?.head) {
|
|
@@ -1243,18 +1501,29 @@ export class Entities {
|
|
|
1243
1501
|
targetYaw = entity.yaw
|
|
1244
1502
|
}
|
|
1245
1503
|
if (typeof targetYaw === 'number' && Number.isFinite(targetYaw)) {
|
|
1246
|
-
const dy = shortestYawRadians(e.rotation.y, targetYaw)
|
|
1247
1504
|
// Stop previous rotation tween to prevent accumulation (mirror _posTween)
|
|
1248
1505
|
e.userData._rotTween?.stop()
|
|
1249
|
-
e.userData._rotTween =
|
|
1506
|
+
e.userData._rotTween = undefined
|
|
1507
|
+
const currentYaw = Number.isFinite(e.rotation.y) ? e.rotation.y : 0
|
|
1508
|
+
const dy = shortestYawRadians(currentYaw, targetYaw)
|
|
1509
|
+
if (rotationTweenDuration === 0) {
|
|
1510
|
+
e.rotation.y = currentYaw + dy
|
|
1511
|
+
} else {
|
|
1512
|
+
e.userData._rotTween = new TWEEN.Tween(e.rotation).to({ y: currentYaw + dy }, rotationTweenDuration).start()
|
|
1513
|
+
}
|
|
1250
1514
|
}
|
|
1251
1515
|
|
|
1252
1516
|
if (e?.playerObject && overrides?.rotation?.head) {
|
|
1253
1517
|
const { playerObject } = e
|
|
1518
|
+
const hy = overrides.rotation.head.y
|
|
1519
|
+
storeNetworkHeadYaw(e.userData, hy, entity.yaw)
|
|
1254
1520
|
playerObject.skin.head.rotation.y = 0
|
|
1255
1521
|
|
|
1256
1522
|
const hp = overrides.rotation.head.x
|
|
1257
|
-
|
|
1523
|
+
storeNetworkHeadPitch(e.userData, hp)
|
|
1524
|
+
applyNetworkHeadPitch(playerObject, e.userData)
|
|
1525
|
+
} else if (e?.playerObject && typeof entity.yaw === 'number' && Number.isFinite(entity.yaw)) {
|
|
1526
|
+
storeNetworkHeadYaw(e.userData, undefined, entity.yaw)
|
|
1258
1527
|
}
|
|
1259
1528
|
}
|
|
1260
1529
|
|
|
@@ -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) {
|