minecraft-renderer 0.1.63 → 0.1.65

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.
@@ -45,14 +45,6 @@ export const TWEEN_DURATION = 120
45
45
 
46
46
  const degreesToRadians = (degrees: number) => degrees * (Math.PI / 180)
47
47
 
48
- const clamp01 = (v: number) => Math.max(0, Math.min(1, v))
49
- const clamp = (v: number, a: number, b: number) => Math.max(a, Math.min(b, v))
50
- const wrapPi = (a: number) => {
51
- a = (a + Math.PI) % (Math.PI * 2)
52
- if (a < 0) a += Math.PI * 2
53
- return a - Math.PI
54
- }
55
-
56
48
  function convert2sComplementToHex(complement: number) {
57
49
  if (complement < 0) {
58
50
  complement = (0xFF_FF_FF_FF + complement + 1) >>> 0
@@ -280,130 +272,45 @@ export class Entities {
280
272
  pendingModelOverrides = new Map<string, { parts: EntityModelOverridePart[] }>()
281
273
 
282
274
  private motionCache = new Map<string, { pos: THREE.Vector3, speed: number }>()
283
- private _wasThirdPerson = false
275
+ private readonly MOVE_ON = 0.05
276
+ private readonly MOVE_OFF = 0.02
277
+ private readonly RUN_ON = 4.8
278
+ private readonly RUN_OFF = 4.2
284
279
 
285
- private _tpBodyYaw = 0
286
- private _tpHasBodyYaw = false
287
-
288
- private updateAutoWalkFlags(entityKey: string, entity: SceneEntity, dt: number, samplePos?: THREE.Vector3) {
280
+ private updateAutoWalkFlags(entityKey: string, entity: SceneEntity, dt: number) {
289
281
  if (!entity.playerObject?.animation) return
290
282
  const anim: any = entity.playerObject.animation
283
+ if (!('isMoving' in anim) || !('isRunning' in anim)) return
291
284
  if (dt <= 0) return
292
285
 
293
- const pos = samplePos ?? entity.position
294
-
295
286
  const cached = this.motionCache.get(entityKey)
296
287
  if (!cached) {
297
- this.motionCache.set(entityKey, { pos: pos.clone(), speed: 0 })
288
+ this.motionCache.set(entityKey, { pos: entity.position.clone(), speed: 0 })
298
289
  anim.isMoving = false
299
290
  anim.isRunning = false
300
- anim.moveAmount = 0
301
- anim.runAmount = 0
302
291
  return
303
292
  }
304
293
 
305
- const dx = pos.x - cached.pos.x
306
- const dz = pos.z - cached.pos.z
307
- cached.pos.copy(pos)
308
-
309
- const instSpeed = Math.hypot(dx, dz) / Math.max(dt, 1e-6) // blocks/sec
310
-
311
- const alpha = 1 - Math.exp(-dt * 12)
312
- cached.speed += (instSpeed - cached.speed) * alpha
313
-
314
- const speed = cached.speed
315
-
316
- const smoothstep = (a: number, b: number, x: number) => {
317
- const t = clamp01((x - a) / (b - a))
318
- return t * t * (3 - 2 * t)
319
- }
320
-
321
- const MOVE_START = 0.08
322
- const MOVE_FULL = 0.6
323
-
324
- const WALK_MAX = 4.3
325
- const SPRINT_FULL = 5.6
326
-
327
- const moveAmount = smoothstep(MOVE_START, MOVE_FULL, speed)
328
- const runAmount = smoothstep(WALK_MAX, SPRINT_FULL, speed)
329
-
330
- anim.moveAmount = moveAmount
331
- anim.runAmount = runAmount
332
-
333
- anim.isMoving = moveAmount > 0.15
334
- anim.isRunning = runAmount > 0.55
335
- }
336
-
337
- private getLocalSneak(): boolean {
338
- const ps: any = this.worldRenderer.playerStateReactive as any
339
- return (
340
- ps?.movementState === 'SNEAKING' ||
341
- ps?.movementState === 'CROUCHING' ||
342
- !!ps?.isSneaking ||
343
- !!ps?.sneaking ||
344
- !!ps?.isCrouching ||
345
- !!ps?.crouching ||
346
- !!ps?.controls?.sneak ||
347
- !!ps?.controlState?.sneak
348
- )
349
- }
350
-
351
- private updateThirdPersonHeadAndBody(entity: SceneEntity, dt: number) {
352
- const anim: any = entity.playerObject?.animation
353
- const rotation = this.worldRenderer.cameraShake.getBaseRotation()
354
-
355
- const camYaw = rotation.yaw
356
- const camPitch = rotation.pitch
357
-
358
- const move = clamp01(anim?.moveAmount ?? (anim?.isMoving ? 1 : 0))
359
- const run = clamp01(anim?.runAmount ?? (anim?.isRunning ? 1 : 0))
360
- const moving = move > 0.15
361
-
362
- if (!this._tpHasBodyYaw) {
363
- this._tpHasBodyYaw = true
364
- this._tpBodyYaw = camYaw
365
- }
366
-
367
- if (moving) {
368
- const k = 1 - Math.exp(-dt * (18 + 10 * run))
369
- this._tpBodyYaw = this._tpBodyYaw + wrapPi(camYaw - this._tpBodyYaw) * k
294
+ const dx = entity.position.x - cached.pos.x
295
+ const dz = entity.position.z - cached.pos.z
296
+ cached.pos.copy(entity.position)
370
297
 
371
- entity.rotation.set(0, this._tpBodyYaw, 0)
298
+ const instSpeed = Math.hypot(dx, dz) / Math.max(dt, 1e-6)
372
299
 
373
- const headMax = ((25 - 8 * run) * Math.PI) / 180
374
- const headYaw = clamp(wrapPi(camYaw - this._tpBodyYaw), -headMax, headMax)
300
+ cached.speed = cached.speed * 0.8 + instSpeed * 0.2
375
301
 
376
- if (anim && 'lookYaw' in anim) anim.lookYaw = headYaw
377
- if (anim && 'lookPitch' in anim) anim.lookPitch = -camPitch
378
- return
379
- }
302
+ const movingNow = anim.isMoving
303
+ ? cached.speed > this.MOVE_OFF
304
+ : cached.speed > this.MOVE_ON
380
305
 
381
- // idle behavior (head leads, body follows after threshold)
382
- const maxHeadYaw = (85 * Math.PI) / 180
383
- const bodyFollowStart = (55 * Math.PI) / 180
306
+ const runningNow = anim.isRunning
307
+ ? cached.speed > this.RUN_OFF
308
+ : cached.speed > this.RUN_ON
384
309
 
385
- let desiredHeadYaw = wrapPi(camYaw - this._tpBodyYaw)
386
- desiredHeadYaw = clamp(desiredHeadYaw, -maxHeadYaw, maxHeadYaw)
387
-
388
- const excess = Math.abs(desiredHeadYaw) - bodyFollowStart
389
- if (excess > 0) {
390
- const push = excess / (maxHeadYaw - bodyFollowStart)
391
- const targetBodyYaw = camYaw - Math.sign(desiredHeadYaw) * bodyFollowStart
392
- const k = 1 - Math.exp(-dt * (6 + 10 * push))
393
- this._tpBodyYaw = this._tpBodyYaw + wrapPi(targetBodyYaw - this._tpBodyYaw) * k
394
- desiredHeadYaw = wrapPi(camYaw - this._tpBodyYaw)
395
- desiredHeadYaw = clamp(desiredHeadYaw, -maxHeadYaw, maxHeadYaw)
396
- } else {
397
- const k = 1 - Math.exp(-dt * 5)
398
- this._tpBodyYaw = this._tpBodyYaw + wrapPi(camYaw - this._tpBodyYaw) * (k * 0.15)
399
- }
400
-
401
- entity.rotation.set(0, this._tpBodyYaw, 0)
402
- if (anim && 'lookYaw' in anim) anim.lookYaw = desiredHeadYaw
403
- if (anim && 'lookPitch' in anim) anim.lookPitch = -camPitch
310
+ anim.isMoving = movingNow
311
+ anim.isRunning = movingNow && runningNow
404
312
  }
405
313
 
406
-
407
314
  get entitiesByName(): Record<string, SceneEntity[]> {
408
315
  const byName: Record<string, SceneEntity[]> = {}
409
316
  for (const entity of Object.values(this.entities)) {
@@ -477,8 +384,6 @@ export class Entities {
477
384
  this.currentSkinUrls = {}
478
385
 
479
386
  this.motionCache.clear()
480
- this._wasThirdPerson = false
481
- this._tpHasBodyYaw = false
482
387
 
483
388
  // Clean up player entity
484
389
  if (this.playerEntity) {
@@ -547,93 +452,67 @@ export class Entities {
547
452
  const botPos = this.worldRenderer.viewerChunkPosition
548
453
  const VISIBLE_DISTANCE = 10 * 10
549
454
 
550
- const thirdPersonNow = this.worldRenderer.playerStateUtils.isThirdPerson()
551
- if (thirdPersonNow !== this._wasThirdPerson) {
552
- this._wasThirdPerson = thirdPersonNow
553
- const key = String(this.playerEntity?.originalEntity.id ?? 'player_entity')
554
- this.motionCache.delete(key)
555
-
556
- const anim: any = this.playerEntity?.playerObject?.animation
557
- if (anim?.resetLocomotion) anim.resetLocomotion()
558
-
559
- this._tpHasBodyYaw = false
560
- }
561
-
562
455
  for (const [entityIdRaw, entity] of [...Object.entries(this.entities), ['player_entity', this.playerEntity] as [string, SceneEntity | null]]) {
563
456
  if (!entity) continue
564
457
 
458
+ let entityKey = entityIdRaw
565
459
  const isPlayerEntity = entityIdRaw === 'player_entity'
566
- const entityKey = isPlayerEntity ? String(this.playerEntity?.originalEntity.id ?? 'player_entity') : String(entityIdRaw)
567
460
 
568
461
  if (isPlayerEntity) {
569
- entity.visible = thirdPersonNow
462
+ const thirdPerson = this.worldRenderer.playerStateUtils.isThirdPerson()
463
+ entity.visible = thirdPerson
570
464
 
571
- if (thirdPersonNow) {
465
+ if (thirdPerson) {
572
466
  const yOffset = this.worldRenderer.playerStateReactive.eyeHeight
573
- // Set world position — proxy auto-converts to scene coords
574
467
  entity.position.set(
575
468
  this.worldRenderer.cameraWorldPos.x,
576
469
  this.worldRenderer.cameraWorldPos.y - yOffset,
577
470
  this.worldRenderer.cameraWorldPos.z
578
471
  )
579
-
580
- const p: any = (this.worldRenderer.playerStateReactive as any).position
581
- if (p && typeof p.x === 'number') {
582
- this.updateAutoWalkFlags(entityKey, entity, dtRaw, new THREE.Vector3(p.x, p.y, p.z))
583
- } else {
584
- const wp = this.worldRenderer.sceneOrigin.getWorldPosition(entity)
585
- this.updateAutoWalkFlags(entityKey, entity, dtRaw, wp ? new THREE.Vector3(wp.x, wp.y, wp.z) : entity.position)
586
- }
587
-
588
- this.updateThirdPersonHeadAndBody(entity, dt)
589
- } else {
590
- const wp = this.worldRenderer.sceneOrigin.getWorldPosition(entity)
591
- this.updateAutoWalkFlags(entityKey, entity, dtRaw, wp ? new THREE.Vector3(wp.x, wp.y, wp.z) : entity.position)
592
472
  }
593
- } else {
594
- const wp = this.worldRenderer.sceneOrigin.getWorldPosition(entity)
595
- this.updateAutoWalkFlags(entityKey, entity, dtRaw, wp ? new THREE.Vector3(wp.x, wp.y, wp.z) : entity.position)
473
+
474
+ entityKey = String(this.playerEntity?.originalEntity.id ?? 'player_entity')
596
475
  }
597
476
 
598
477
  const { playerObject } = entity
599
478
 
600
- if (playerObject?.animation) {
601
- const anim: any = playerObject.animation
602
-
603
- const flags = Number((entity.originalEntity?.metadata?.[0] ?? 0))
604
- const remoteSneak = (flags & 0x02) !== 0
605
-
606
- const localSneak = this.getLocalSneak()
607
-
608
- if ('isCrouched' in anim) anim.isCrouched = isPlayerEntity ? localSneak : remoteSneak
479
+ this.updateAutoWalkFlags(entityKey, entity, dtRaw)
609
480
 
481
+ if (playerObject?.animation) {
610
482
  playerObject.animation.update(playerObject, dt)
611
483
  }
612
484
 
613
- // Update GLTF animations
614
485
  entity.traverse(child => {
615
486
  if (child instanceof Entity.EntityMesh) {
616
487
  child.update(dt)
617
488
  }
618
489
  })
619
490
 
620
- // Update visibility based on distance and chunk load status
621
491
  if (!isPlayerEntity && botPos && entity.position) {
622
492
  const dx = entity.position.x - botPos.x
623
493
  const dy = entity.position.y - botPos.y
624
494
  const dz = entity.position.z - botPos.z
625
495
  const distanceSquared = dx * dx + dy * dy + dz * dz
626
496
 
627
- // Entity is visible if within 20 blocks OR in a finished chunk
628
497
  entity.visible = !!(distanceSquared < VISIBLE_DISTANCE || this.worldRenderer.shouldObjectVisible(entity))
629
498
 
630
499
  this.maybeRenderPlayerSkin(entityIdRaw)
631
500
  }
632
501
 
633
502
  if (entity.visible) {
634
- // Update armor positions
635
503
  this.syncArmorPositions(entity)
636
504
  }
505
+
506
+ if (isPlayerEntity && entity.visible) {
507
+ const rotation = this.worldRenderer.cameraShake.getBaseRotation()
508
+ entity.rotation.set(0, rotation.yaw, 0)
509
+
510
+ entity.traverse((c) => {
511
+ if (c.name === 'head') {
512
+ c.rotation.set(-rotation.pitch, 0, 0)
513
+ }
514
+ })
515
+ }
637
516
  }
638
517
  }
639
518
 
@@ -1393,8 +1272,20 @@ export class Entities {
1393
1272
  })
1394
1273
  .start()
1395
1274
  }
1396
- if (typeof entity.yaw === 'number' && Number.isFinite(entity.yaw)) {
1397
- const dy = shortestYawRadians(e.rotation.y, entity.yaw)
1275
+ /** World yaw for the whole model: for PlayerObject skins, rotate body to head look dir; head mesh stays yaw-fixed (pitch only). */
1276
+ let targetYaw: number | undefined
1277
+ if (e.playerObject && overrides?.rotation?.head) {
1278
+ const hy = overrides.rotation.head.y
1279
+ const headYawWorld =
1280
+ typeof hy === 'number' && Number.isFinite(hy) ? hy : entity.yaw
1281
+ if (typeof headYawWorld === 'number' && Number.isFinite(headYawWorld)) {
1282
+ targetYaw = headYawWorld
1283
+ }
1284
+ } else if (typeof entity.yaw === 'number' && Number.isFinite(entity.yaw)) {
1285
+ targetYaw = entity.yaw
1286
+ }
1287
+ if (typeof targetYaw === 'number' && Number.isFinite(targetYaw)) {
1288
+ const dy = shortestYawRadians(e.rotation.y, targetYaw)
1398
1289
  // Stop previous rotation tween to prevent accumulation (mirror _posTween)
1399
1290
  e.userData._rotTween?.stop()
1400
1291
  e.userData._rotTween = new TWEEN.Tween(e.rotation)
@@ -1404,14 +1295,7 @@ export class Entities {
1404
1295
 
1405
1296
  if (e?.playerObject && overrides?.rotation?.head) {
1406
1297
  const { playerObject } = e
1407
- const hy = overrides.rotation.head.y
1408
- const headYawWorld =
1409
- typeof hy === 'number' && Number.isFinite(hy) ? hy : entity.yaw
1410
- const headYawOffset =
1411
- typeof headYawWorld === 'number' && typeof entity.yaw === 'number' && Number.isFinite(headYawWorld) && Number.isFinite(entity.yaw)
1412
- ? shortestYawRadians(entity.yaw, headYawWorld)
1413
- : 0
1414
- playerObject.skin.head.rotation.y = headYawOffset
1298
+ playerObject.skin.head.rotation.y = 0
1415
1299
 
1416
1300
  const hp = overrides.rotation.head.x
1417
1301
  playerObject.skin.head.rotation.x =