minecraft-renderer 0.1.92 → 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 +68 -68
- 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/config.ts +1 -1
- package/src/graphicsBackend/types.ts +4 -1
- package/src/lib/worldrendererCommon.ts +3 -3
- package/src/three/entities.ts +329 -43
- 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 +54 -27
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
|
/**
|
|
@@ -22,7 +22,7 @@ export const defaultWorldRendererConfig = {
|
|
|
22
22
|
futuristicReveal: false,
|
|
23
23
|
|
|
24
24
|
/** Master toggle for section occlusion graph (smart / cave cull). Disabled in spectator. */
|
|
25
|
-
smartCull:
|
|
25
|
+
smartCull: false,
|
|
26
26
|
|
|
27
27
|
// Performance settings
|
|
28
28
|
wasmMesher: true,
|
|
@@ -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,
|
|
@@ -57,7 +57,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
57
57
|
disableEntities: false,
|
|
58
58
|
/** Tint section borders by occlusion BFS step (perf debug overlay). */
|
|
59
59
|
caveCullingDebug: false,
|
|
60
|
-
smartCull:
|
|
60
|
+
smartCull: false
|
|
61
61
|
// disableParticles: false
|
|
62
62
|
})
|
|
63
63
|
|
|
@@ -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[]> = {}
|
|
@@ -316,6 +354,7 @@ export class Entities {
|
|
|
316
354
|
}
|
|
317
355
|
|
|
318
356
|
handlePlayerEntity(playerData: SceneEntity['originalEntity']) {
|
|
357
|
+
if (!this.isRenderingAllowed()) return
|
|
319
358
|
// Create player entity if it doesn't exist
|
|
320
359
|
if (!this.playerEntity) {
|
|
321
360
|
// Create the player entity similar to how normal entities are created
|
|
@@ -329,7 +368,7 @@ export class Entities {
|
|
|
329
368
|
|
|
330
369
|
group.name = 'player_entity'
|
|
331
370
|
this.playerEntity = group
|
|
332
|
-
this.
|
|
371
|
+
this.syncSceneAttachment(group)
|
|
333
372
|
|
|
334
373
|
void this.updatePlayerSkin(playerData.id, playerData.username, playerData.uuid ?? undefined, stevePngUrl)
|
|
335
374
|
}
|
|
@@ -392,9 +431,27 @@ export class Entities {
|
|
|
392
431
|
}
|
|
393
432
|
|
|
394
433
|
setRendering(rendering: boolean, entity: THREE.Object3D | null = null) {
|
|
395
|
-
this.currentlyRendering = rendering
|
|
396
|
-
|
|
397
|
-
|
|
434
|
+
this.currentlyRendering = rendering && this.isRenderingAllowed()
|
|
435
|
+
this.setSceneAttached(this.currentlyRendering, entity)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
isRenderingAllowed(): boolean {
|
|
439
|
+
return this.worldRenderer.worldRendererConfig.renderEntities !== false && !this.worldRenderer.reactiveDebugParams.disableEntities
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/** Add or remove entity meshes from the scene (including third-person player). */
|
|
443
|
+
syncSceneAttachment(entity: THREE.Object3D | null = null): void {
|
|
444
|
+
const attached = this.isRenderingAllowed()
|
|
445
|
+
this.currentlyRendering = attached
|
|
446
|
+
this.setSceneAttached(attached, entity)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
private setSceneAttached(attached: boolean, entity: THREE.Object3D | null = null): void {
|
|
450
|
+
const targets = entity
|
|
451
|
+
? [entity]
|
|
452
|
+
: [...Object.values(this.entities), ...(this.playerEntity ? [this.playerEntity] : [])]
|
|
453
|
+
for (const ent of targets) {
|
|
454
|
+
if (attached) {
|
|
398
455
|
if (!this.worldRenderer.scene.children.includes(ent)) this.worldRenderer.scene.add(ent)
|
|
399
456
|
} else {
|
|
400
457
|
this.worldRenderer.scene.remove(ent)
|
|
@@ -414,10 +471,8 @@ export class Entities {
|
|
|
414
471
|
}
|
|
415
472
|
|
|
416
473
|
render() {
|
|
417
|
-
|
|
418
|
-
if (
|
|
419
|
-
this.setRendering(renderEntitiesConfig)
|
|
420
|
-
}
|
|
474
|
+
this.syncSceneAttachment()
|
|
475
|
+
if (!this.isRenderingAllowed()) return
|
|
421
476
|
|
|
422
477
|
this.clock.update(performance.now())
|
|
423
478
|
const dt = Math.min(this.clock.getDelta(), 1 / 30)
|
|
@@ -433,7 +488,8 @@ export class Entities {
|
|
|
433
488
|
const thirdPerson = this.worldRenderer.playerStateUtils.isThirdPerson()
|
|
434
489
|
entity.visible = thirdPerson
|
|
435
490
|
|
|
436
|
-
|
|
491
|
+
const isAnchoredPassenger = entity.userData._passengerVehicleId != null || entity.userData._boatPassengerVehicleId != null
|
|
492
|
+
if (thirdPerson && !isAnchoredPassenger) {
|
|
437
493
|
const yOffset = this.worldRenderer.playerStateReactive.eyeHeight
|
|
438
494
|
entity.position.set(this.worldRenderer.cameraWorldPos.x, this.worldRenderer.cameraWorldPos.y - yOffset, this.worldRenderer.cameraWorldPos.z)
|
|
439
495
|
}
|
|
@@ -445,6 +501,31 @@ export class Entities {
|
|
|
445
501
|
playerObject.animation.update(playerObject, dt)
|
|
446
502
|
}
|
|
447
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
|
+
|
|
448
529
|
entity.traverse(child => {
|
|
449
530
|
if (child instanceof Entity.EntityMesh) {
|
|
450
531
|
child.update(dt)
|
|
@@ -465,23 +546,124 @@ export class Entities {
|
|
|
465
546
|
this.maybeRenderPlayerSkin(entityIdRaw)
|
|
466
547
|
}
|
|
467
548
|
|
|
468
|
-
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) {
|
|
469
555
|
this.syncArmorPositions(entity)
|
|
470
556
|
}
|
|
557
|
+
}
|
|
471
558
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
559
|
+
this.updateVehiclePassengerPositions()
|
|
560
|
+
this.applyRemoteBoatPassengerRotations()
|
|
561
|
+
this.applyLocalThirdPersonPlayerRotation()
|
|
562
|
+
this.updateBoatPaddleAnimations(dt)
|
|
563
|
+
}
|
|
475
564
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
+
)
|
|
482
590
|
}
|
|
483
591
|
}
|
|
484
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
|
|
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()
|
|
665
|
+
}
|
|
666
|
+
|
|
485
667
|
private syncArmorPositions(entity: SceneEntity) {
|
|
486
668
|
if (!entity.playerObject) return
|
|
487
669
|
|
|
@@ -746,7 +928,10 @@ export class Entities {
|
|
|
746
928
|
}
|
|
747
929
|
}
|
|
748
930
|
|
|
749
|
-
private applyMovementAnimation(
|
|
931
|
+
private applyMovementAnimation(
|
|
932
|
+
playerObject: PlayerObjectType,
|
|
933
|
+
animation: 'walking' | 'running' | 'oneSwing' | 'idle' | 'crouch' | 'crouchWalking' | 'riding'
|
|
934
|
+
): void {
|
|
750
935
|
const anim = playerObject.animation as WalkingGeneralSwing | undefined
|
|
751
936
|
if (!anim) return
|
|
752
937
|
|
|
@@ -756,12 +941,21 @@ export class Entities {
|
|
|
756
941
|
}
|
|
757
942
|
|
|
758
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
|
|
759
953
|
anim.isMoving = animation === 'walking' || animation === 'running' || animation === 'crouchWalking'
|
|
760
954
|
anim.isRunning = animation === 'running'
|
|
761
955
|
anim.isCrouched = animation === 'crouch' || animation === 'crouchWalking'
|
|
762
956
|
}
|
|
763
957
|
|
|
764
|
-
playAnimation(entityPlayerId, animation: 'walking' | 'running' | 'oneSwing' | 'idle' | 'crouch' | 'crouchWalking') {
|
|
958
|
+
playAnimation(entityPlayerId, animation: 'walking' | 'running' | 'oneSwing' | 'idle' | 'crouch' | 'crouchWalking' | 'riding') {
|
|
765
959
|
const playerObject =
|
|
766
960
|
entityPlayerId === 'player_entity' ? this.playerEntity?.playerObject : (this.getPlayerObject(entityPlayerId) ?? this.playerEntity?.playerObject)
|
|
767
961
|
|
|
@@ -881,6 +1075,10 @@ export class Entities {
|
|
|
881
1075
|
if (!e) return
|
|
882
1076
|
e.userData._posTween?.stop()
|
|
883
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)
|
|
884
1082
|
if (e.additionalCleanup) e.additionalCleanup()
|
|
885
1083
|
e.traverse(c => {
|
|
886
1084
|
if (c['additionalCleanup']) c['additionalCleanup']()
|
|
@@ -892,6 +1090,7 @@ export class Entities {
|
|
|
892
1090
|
delete this.entities[entity.id]
|
|
893
1091
|
return
|
|
894
1092
|
}
|
|
1093
|
+
if (!this.isRenderingAllowed()) return
|
|
895
1094
|
|
|
896
1095
|
let mesh: THREE.Object3D | undefined
|
|
897
1096
|
if (e === undefined) {
|
|
@@ -971,6 +1170,11 @@ export class Entities {
|
|
|
971
1170
|
}
|
|
972
1171
|
if (!mesh) return
|
|
973
1172
|
mesh.name = 'mesh'
|
|
1173
|
+
const boatMeshYawOffset = getBoatMeshYawOffset(entity.name)
|
|
1174
|
+
if (boatMeshYawOffset != null) {
|
|
1175
|
+
mesh.rotation.y = boatMeshYawOffset
|
|
1176
|
+
setupBoatMesh(mesh)
|
|
1177
|
+
}
|
|
974
1178
|
// set initial position so there are no weird jumps update after
|
|
975
1179
|
const pos = entity.pos ?? entity.position
|
|
976
1180
|
this.worldRenderer.sceneOrigin.track(group)
|
|
@@ -985,7 +1189,6 @@ export class Entities {
|
|
|
985
1189
|
group.add(mesh)
|
|
986
1190
|
group.add(boxHelper)
|
|
987
1191
|
boxHelper.visible = false
|
|
988
|
-
this.worldRenderer.scene.add(group)
|
|
989
1192
|
|
|
990
1193
|
e = group
|
|
991
1194
|
e.name = 'entity'
|
|
@@ -996,13 +1199,23 @@ export class Entities {
|
|
|
996
1199
|
void this.updatePlayerSkin(entity.id, entity.username, overrides?.texture ? entity.uuid : undefined, overrides?.texture || stevePngUrl)
|
|
997
1200
|
}
|
|
998
1201
|
this.setDebugMode(this.debugMode, group)
|
|
999
|
-
this.
|
|
1202
|
+
this.syncSceneAttachment(group)
|
|
1000
1203
|
|
|
1001
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
|
+
}
|
|
1002
1210
|
} else {
|
|
1003
1211
|
mesh = e.children.find(c => c.name === 'mesh')
|
|
1212
|
+
if (entity.renderHints) {
|
|
1213
|
+
e.originalEntity.renderHints = entity.renderHints
|
|
1214
|
+
}
|
|
1004
1215
|
}
|
|
1005
1216
|
|
|
1217
|
+
this.applyEntityRenderHints(e, entity)
|
|
1218
|
+
|
|
1006
1219
|
// Update equipment
|
|
1007
1220
|
this.updateEntityEquipment(e, entity)
|
|
1008
1221
|
|
|
@@ -1194,26 +1407,88 @@ export class Entities {
|
|
|
1194
1407
|
this.updateEntityPosition(entity, justAdded, overrides)
|
|
1195
1408
|
}
|
|
1196
1409
|
|
|
1197
|
-
|
|
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
|
+
) {
|
|
1198
1452
|
const e = this.entities[entity.id]
|
|
1199
1453
|
if (!e) return
|
|
1200
|
-
|
|
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)
|
|
1201
1462
|
if (entity.position) {
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
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)
|
|
1206
1489
|
}
|
|
1207
|
-
// Stop previous position tween to prevent accumulation
|
|
1208
|
-
e.userData._posTween?.stop()
|
|
1209
|
-
// Tween a separate target object, apply via proxy on each update
|
|
1210
|
-
e.userData._posTween = new TWEEN.Tween(e.userData._tweenTarget)
|
|
1211
|
-
.to({ x: entity.position.x, y: entity.position.y, z: entity.position.z }, ANIMATION_DURATION)
|
|
1212
|
-
.onUpdate(() => {
|
|
1213
|
-
e.position.set(e.userData._tweenTarget.x, e.userData._tweenTarget.y, e.userData._tweenTarget.z)
|
|
1214
|
-
})
|
|
1215
|
-
.start()
|
|
1216
1490
|
}
|
|
1491
|
+
const rotationTweenDuration = getEntityRotationTweenDurationMs(entity, justAdded)
|
|
1217
1492
|
/** World yaw for the whole model: for PlayerObject skins, rotate body to head look dir; head mesh stays yaw-fixed (pitch only). */
|
|
1218
1493
|
let targetYaw: number | undefined
|
|
1219
1494
|
if (e.playerObject && overrides?.rotation?.head) {
|
|
@@ -1226,18 +1501,29 @@ export class Entities {
|
|
|
1226
1501
|
targetYaw = entity.yaw
|
|
1227
1502
|
}
|
|
1228
1503
|
if (typeof targetYaw === 'number' && Number.isFinite(targetYaw)) {
|
|
1229
|
-
const dy = shortestYawRadians(e.rotation.y, targetYaw)
|
|
1230
1504
|
// Stop previous rotation tween to prevent accumulation (mirror _posTween)
|
|
1231
1505
|
e.userData._rotTween?.stop()
|
|
1232
|
-
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
|
+
}
|
|
1233
1514
|
}
|
|
1234
1515
|
|
|
1235
1516
|
if (e?.playerObject && overrides?.rotation?.head) {
|
|
1236
1517
|
const { playerObject } = e
|
|
1518
|
+
const hy = overrides.rotation.head.y
|
|
1519
|
+
storeNetworkHeadYaw(e.userData, hy, entity.yaw)
|
|
1237
1520
|
playerObject.skin.head.rotation.y = 0
|
|
1238
1521
|
|
|
1239
1522
|
const hp = overrides.rotation.head.x
|
|
1240
|
-
|
|
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)
|
|
1241
1527
|
}
|
|
1242
1528
|
}
|
|
1243
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
|