@adatechnology/game-characters-three 0.6.0 → 0.7.0

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/index.js CHANGED
@@ -993,7 +993,7 @@ var createBestBuilder = (host, { types, random = Math.random }) => {
993
993
  });
994
994
  }
995
995
  const neckLen = 0.14 + Math.max(0, P.neck);
996
- if (!cut.head) add(U, CYL(0.058, 0.068, neckLen, SEG), skinM, 0, 1.63 + Math.max(0, P.neck) / 2, 0);
996
+ const neck = cut.head ? void 0 : add(U, CYL(0.058, 0.068, neckLen, SEG), skinM, 0, 1.63 + Math.max(0, P.neck) / 2, 0);
997
997
  const headG = new THREE.Group();
998
998
  headG.position.set(0, 1.64 + P.neck, P.headFwd || 0);
999
999
  headG.rotation.x = P.headX;
@@ -1200,6 +1200,7 @@ var createBestBuilder = (host, { types, random = Math.random }) => {
1200
1200
  if (o.userData.flap) flaps.push(o);
1201
1201
  });
1202
1202
  const rig = { flaps, dangles: arms.filter((a) => a.el.userData.dangle), type: spec.type || "Lento", fig, upper, headG, arms, legs, P, ph: spec.phase ?? options.random() * 6, dropBase: fig.position.y, leanBase: P.lean, hxBase: P.headX };
1203
+ if (neck) Object.defineProperty(rig, "neck", { value: neck });
1203
1204
  if (spec.lying) {
1204
1205
  root.rotation.x = -PI / 2;
1205
1206
  root.position.set(0, 0.16 * P.s, 0.9 * P.s);
@@ -3751,7 +3752,8 @@ function createCharactersTestModule(options = {}) {
3751
3752
  const makeKit = () => options.createKit ? options.createKit({ penetration: settings.penetration, deathRule: settings.deathRule }) : createCharacters({ ...options.packs ? { packs: options.packs } : {}, penetration: settings.penetration, damage: { deathRule: settings.deathRule } });
3752
3753
  let kit = makeKit();
3753
3754
  let undead = options.createUndead ? options.createUndead(kit) : null;
3754
- const random = createRng3(options.seed ?? 1);
3755
+ let stream = createRng3(options.seed ?? 1);
3756
+ const random = () => stream();
3755
3757
  let seedCounter = 100;
3756
3758
  let host = null;
3757
3759
  let three = null;
@@ -4131,6 +4133,14 @@ function createCharactersTestModule(options = {}) {
4131
4133
  wounds = createWounds(lent.THREE, { random, scene: scene2, groundAt: (x, z) => lent.groundAt(x, z), ...bridge ? { bridge } : {}, ...sound ? { sound } : {} });
4132
4134
  for (const entry of entries.values()) trackEntry(entry);
4133
4135
  const stopChange = lent.bench?.onChange(onChange);
4136
+ const stopReset = lent.onReset?.(() => {
4137
+ stream = createRng3(options.seed ?? 1);
4138
+ seedCounter = 100;
4139
+ time = 0;
4140
+ selected = null;
4141
+ lastCrossed = 0;
4142
+ lastEnergy = 1;
4143
+ });
4134
4144
  const stopEvents = lent.on((event) => {
4135
4145
  if (event.type !== "explosion") return;
4136
4146
  for (const entry of entries.values()) {
@@ -4155,6 +4165,7 @@ function createCharactersTestModule(options = {}) {
4155
4165
  });
4156
4166
  return () => {
4157
4167
  stopChange?.();
4168
+ stopReset?.();
4158
4169
  stopEvents();
4159
4170
  stopFrame();
4160
4171
  clearOverlays();
@@ -4201,6 +4212,7 @@ import {
4201
4212
  filterPoseChannels,
4202
4213
  samplePoseClip,
4203
4214
  walkCadence,
4215
+ stepCascade as stepCascade2,
4204
4216
  walkGait
4205
4217
  } from "@adatechnology/game-characters";
4206
4218
 
@@ -4276,8 +4288,22 @@ var ARM_REACH = {
4276
4288
  slack: 0.96,
4277
4289
  /** Where the elbow points: down and out from the body, a little back (body space, x outward). */
4278
4290
  elbowPole: [0.55, -1, -0.25],
4291
+ /** The support arm's elbow: under the gun, hardly out of the body (the first-person arms' `elbowPoleLeft`), so the forearm meets the handguard nearer the aim's line. */
4292
+ elbowPoleSupport: [0.1, -1, -0.2],
4279
4293
  /** From the wrist joint to the middle of the palm, metres of the body. */
4280
- palm: 0.042
4294
+ palm: 0.042,
4295
+ /** In a view the elbow keeps at least this interior bend where a hand is out of reach, right then left (the first-person arms' numbers). */
4296
+ viewBend: [1.35, 0.95],
4297
+ /** In a view a hand stays this far before a surface, metres. */
4298
+ contactMargin: 5e-3,
4299
+ /** In a view a hand is never nearer than this to the eye, metres. */
4300
+ contactNearest: 0.12,
4301
+ /** How far in front of the eye a surface is looked for, metres. */
4302
+ contactProbe: 1.4,
4303
+ /** How far a hand reaches forward of its middle at least (the thickness of the palm), and per metre of fingers pointing ahead. */
4304
+ contactPalm: 0.03,
4305
+ /** From the middle of the palm to the fingertips. */
4306
+ contactFingers: 0.085
4281
4307
  };
4282
4308
  var createArmReach = (THREE, rig, hands) => {
4283
4309
  const arms = {
@@ -4298,11 +4324,17 @@ var createArmReach = (THREE, rig, hands) => {
4298
4324
  const v = { shoulder: new THREE.Vector3(), elbow: new THREE.Vector3(), wrist: new THREE.Vector3(), palm: new THREE.Vector3(), target: new THREE.Vector3(), pole: new THREE.Vector3(), a: new THREE.Vector3(), b: new THREE.Vector3(), c: new THREE.Vector3() };
4299
4325
  const q = { hand: new THREE.Quaternion(), parent: new THREE.Quaternion(), body: new THREE.Quaternion(), local: new THREE.Quaternion(), swing: new THREE.Quaternion(), twist: new THREE.Quaternion(), rest: new THREE.Quaternion() };
4300
4326
  const basis = new THREE.Matrix4();
4327
+ const camera = { position: new THREE.Vector3(), quaternion: new THREE.Quaternion(), inverse: new THREE.Quaternion(), probe: new THREE.Vector3(), forward: new THREE.Vector3(), fingers: new THREE.Vector3(), hand: new THREE.Quaternion() };
4328
+ let bendLimit = null;
4301
4329
  const yAxis = new THREE.Vector3(0, 1, 0);
4302
4330
  const lengthsOf = (arm) => {
4303
4331
  const scale = arm.g.getWorldScale(v.a).y;
4304
4332
  return { upper: arm.el.position.length() * scale, fore: arm.hand.position.length() * scale, palm: ARM_REACH.palm * scale };
4305
4333
  };
4334
+ const limitOf = (side, upperLength, fore) => {
4335
+ if (!bendLimit) return (upperLength + fore) * ARM_REACH.slack;
4336
+ return Math.sqrt(upperLength * upperLength + fore * fore + 2 * upperLength * fore * Math.cos(bendLimit[side === "R" ? 0 : 1]));
4337
+ };
4306
4338
  const wristOf = (side, pose, palm) => {
4307
4339
  q.hand.set(...pose.quaternion).multiply(handBasis[side]);
4308
4340
  v.palm.set(...pose.position);
@@ -4311,14 +4343,14 @@ var createArmReach = (THREE, rig, hands) => {
4311
4343
  const turnChest = (pose) => {
4312
4344
  const { upper: up, fore, palm } = lengthsOf(arms.L);
4313
4345
  wristOf("L", pose, palm);
4314
- const excess = arms.L.g.getWorldPosition(v.shoulder).distanceTo(v.wrist) - (up + fore) * ARM_REACH.slack;
4346
+ const excess = arms.L.g.getWorldPosition(v.shoulder).distanceTo(v.wrist) - limitOf("L", up, fore);
4315
4347
  if (excess <= 0) return;
4316
4348
  const chestRadius = Math.hypot(arms.L.g.position.x, arms.L.g.position.z) * arms.L.g.getWorldScale(v.a).y;
4317
4349
  upper.rotation.y -= Math.min(ARM_REACH.chestTurn, excess / Math.max(0.05, chestRadius));
4318
4350
  upper.updateMatrixWorld(true);
4319
4351
  };
4320
- const protract = (arm, length) => {
4321
- const excess = arm.g.getWorldPosition(v.shoulder).distanceTo(v.wrist) - length * ARM_REACH.slack;
4352
+ const protract = (arm, limit) => {
4353
+ const excess = arm.g.getWorldPosition(v.shoulder).distanceTo(v.wrist) - limit;
4322
4354
  if (excess <= 0) return;
4323
4355
  const parent = arm.g.parent;
4324
4356
  v.b.copy(v.wrist).sub(v.shoulder).normalize().multiplyScalar(Math.min(excess, ARM_REACH.protraction)).add(v.shoulder);
@@ -4339,11 +4371,13 @@ var createArmReach = (THREE, rig, hands) => {
4339
4371
  const arm = arms[side];
4340
4372
  const { upper: up, fore, palm } = lengthsOf(arm);
4341
4373
  wristOf(side, pose, palm);
4342
- protract(arm, up + fore);
4374
+ protract(arm, limitOf(side, up, fore));
4343
4375
  arm.g.getWorldPosition(v.shoulder);
4344
4376
  upper.getWorldQuaternion(q.body);
4345
- v.pole.set(ARM_REACH.elbowPole[0] * arm.outward, ARM_REACH.elbowPole[1], ARM_REACH.elbowPole[2]).applyQuaternion(q.body);
4346
- const most = Math.sqrt(up * up + fore * fore + 2 * up * fore * Math.cos(ARM_REACH.elbowMin));
4377
+ const pole = side === "L" ? ARM_REACH.elbowPoleSupport : ARM_REACH.elbowPole;
4378
+ v.pole.set(pole[0] * arm.outward, pole[1], pole[2]).applyQuaternion(q.body);
4379
+ const lockLimit = Math.sqrt(up * up + fore * fore + 2 * up * fore * Math.cos(ARM_REACH.elbowMin));
4380
+ const most = bendLimit ? Math.min(lockLimit, limitOf(side, up, fore)) : lockLimit;
4347
4381
  const least = Math.sqrt(up * up + fore * fore + 2 * up * fore * Math.cos(ARM_REACH.elbowMax));
4348
4382
  const line = v.c.copy(v.wrist).sub(v.shoulder);
4349
4383
  const distance2 = Math.min(most, Math.max(least, line.length()));
@@ -4368,6 +4402,20 @@ var createArmReach = (THREE, rig, hands) => {
4368
4402
  arm.el.getWorldQuaternion(q.parent);
4369
4403
  arm.hand.quaternion.copy(limitWrist(q.local.copy(q.parent.invert()).multiply(q.hand)));
4370
4404
  };
4405
+ const keepOutside = (pose, outside) => {
4406
+ const place = v.a.set(...pose.position).sub(camera.position).applyQuaternion(camera.inverse);
4407
+ camera.probe.set(place.x, place.y, 0).applyQuaternion(camera.quaternion).add(camera.position);
4408
+ camera.forward.set(0, 0, -1).applyQuaternion(camera.quaternion);
4409
+ const distance2 = outside.contact([camera.probe.x, camera.probe.y, camera.probe.z], [camera.forward.x, camera.forward.y, camera.forward.z], ARM_REACH.contactProbe);
4410
+ if (distance2 === null) return pose;
4411
+ camera.hand.set(...pose.quaternion).premultiply(camera.inverse);
4412
+ const ahead = Math.max(ARM_REACH.contactPalm, -camera.fingers.set(1, 0, 0).applyQuaternion(camera.hand).z * ARM_REACH.contactFingers);
4413
+ const limit = Math.max(outside.nearest ?? ARM_REACH.contactNearest, distance2 - ahead - (outside.margin ?? ARM_REACH.contactMargin));
4414
+ if (-place.z <= limit) return pose;
4415
+ place.z = -limit;
4416
+ place.applyQuaternion(camera.quaternion).add(camera.position);
4417
+ return { ...pose, position: [place.x, place.y, place.z] };
4418
+ };
4371
4419
  const save = () => {
4372
4420
  saved.twist = upper.rotation.y;
4373
4421
  for (const side of ["R", "L"]) {
@@ -4380,14 +4428,24 @@ var createArmReach = (THREE, rig, hands) => {
4380
4428
  }
4381
4429
  };
4382
4430
  return {
4383
- apply(targets) {
4431
+ apply(targets, view) {
4384
4432
  if (!targets || !targets.R && !targets.L) return;
4385
4433
  save();
4386
4434
  applied = true;
4387
4435
  upper.updateWorldMatrix(true, true);
4388
- if (targets.L && targets.R) turnChest(targets.L);
4389
- if (targets.R) reach("R", targets.R);
4390
- if (targets.L) reach("L", targets.L);
4436
+ let { R, L } = targets;
4437
+ bendLimit = view ? view.reachBend ?? ARM_REACH.viewBend : null;
4438
+ if (view?.keepOutside) {
4439
+ view.camera.updateWorldMatrix(true, false);
4440
+ view.camera.matrixWorld.decompose(camera.position, camera.quaternion, v.a);
4441
+ camera.inverse.copy(camera.quaternion).invert();
4442
+ if (R) R = keepOutside(R, view.keepOutside);
4443
+ if (L) L = keepOutside(L, view.keepOutside);
4444
+ }
4445
+ if (L && R) turnChest(L);
4446
+ if (R) reach("R", R);
4447
+ if (L) reach("L", L);
4448
+ bendLimit = null;
4391
4449
  },
4392
4450
  restore() {
4393
4451
  if (!applied) return;
@@ -4405,12 +4463,56 @@ var createArmReach = (THREE, rig, hands) => {
4405
4463
  };
4406
4464
  };
4407
4465
 
4466
+ // src/player/head-hiding.ts
4467
+ var isDrawable = (object) => {
4468
+ const flagged = object;
4469
+ return Boolean(flagged.isMesh || flagged.isSprite || flagged.isPoints);
4470
+ };
4471
+ var createHeadHider = (roots) => {
4472
+ let isHiding = false;
4473
+ let viewCamera = null;
4474
+ const drawables = [];
4475
+ for (const root of roots) root.traverse((object) => isDrawable(object) && drawables.push(object));
4476
+ for (const drawable of drawables) {
4477
+ const previousBefore = drawable.onBeforeRender;
4478
+ const previousAfter = drawable.onAfterRender;
4479
+ let saved = [];
4480
+ drawable.onBeforeRender = (renderer, scene2, camera, geometry, material, group) => {
4481
+ previousBefore.call(drawable, renderer, scene2, camera, geometry, material, group);
4482
+ if (!isHiding || camera !== viewCamera) return;
4483
+ const drawn = Array.isArray(drawable.material) ? drawable.material : [material];
4484
+ saved = drawn.map((entry) => ({ material: entry, colorWrite: entry.colorWrite, depthWrite: entry.depthWrite }));
4485
+ for (const entry of drawn) {
4486
+ entry.colorWrite = false;
4487
+ entry.depthWrite = false;
4488
+ }
4489
+ };
4490
+ drawable.onAfterRender = (renderer, scene2, camera, geometry, material, group) => {
4491
+ previousAfter.call(drawable, renderer, scene2, camera, geometry, material, group);
4492
+ for (const flags of saved) {
4493
+ flags.material.colorWrite = flags.colorWrite;
4494
+ flags.material.depthWrite = flags.depthWrite;
4495
+ }
4496
+ saved = [];
4497
+ };
4498
+ }
4499
+ return {
4500
+ setView(mode, camera) {
4501
+ isHiding = mode === "first" && camera !== null;
4502
+ viewCamera = camera;
4503
+ }
4504
+ };
4505
+ };
4506
+
4408
4507
  // src/player/avatar.ts
4409
4508
  var TWO_PI = Math.PI * 2;
4410
4509
  var MAX_GAIT = 1.4;
4411
4510
  var CLIP_BLEND = 0.12;
4412
4511
  var CLIP_CASCADE = 5;
4413
4512
  var FALL_SECONDS2 = 0.3;
4513
+ var BUILDER_EYE = [0, 1.815, 0.16];
4514
+ var EYE_OFFSET = [0, -0.068, 0];
4515
+ var EYE_BOB = 0.5;
4414
4516
  var HIP_HEIGHT = 0.95;
4415
4517
  var WALK_ARM = 0.28;
4416
4518
  var WALK_BOB = 0.035;
@@ -4420,7 +4522,11 @@ var RUN_BOB = 0.05;
4420
4522
  var REST_ARM_Z = 0.18;
4421
4523
  var CROUCH = { thigh: -0.95, thighWalk: 0.3, shin: 1.9, lean: 0.25, drop: -0.35 };
4422
4524
  var AIMED = { shoulder: -1.42, perAim: 0.85, swing: 0.02, zRight: -0.54, zLeft: 0.36, elbowRight: -0.04, elbowLeft: -0.12 };
4525
+ var SIT = { thigh: -1.5, lean: -0.08, arm: -0.45, armZ: 0.12, elbow: -0.75 };
4526
+ var LIE = { knee: 0.08, armZ: 0.12, elbow: -0.1 };
4423
4527
  var DOWN = { thighWalk: 0.5, shinSwing: 0.85, shinBend: 1.3, armHigh: -2.8, armLow: -0.35, armWave: 0.3, armZ: 0.5, armZWave: 0.3, tilt: 1.5, roll: 0.3, hipY: 0.16, hipZ: 0.6, hipZFlip: 1.2 };
4528
+ var DOOR_SHOULDER = 1.42;
4529
+ var DOOR_ARM = 0.62;
4424
4530
  var HAND_OFFSET = { x: 0, y: -0.33, z: 0.04 };
4425
4531
  var hexOf = (value) => typeof value === "string" ? Number.parseInt(value, 16) : value;
4426
4532
  var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
@@ -4450,6 +4556,8 @@ var createPlayerAvatar = (THREE, options = {}) => {
4450
4556
  const fallSeconds = options.fallSeconds ?? FALL_SECONDS2;
4451
4557
  const smooth = options.cascade ?? true;
4452
4558
  const clipCascade = options.clipCascade ?? CLIP_CASCADE;
4559
+ const eyeBob = options.eyeBob ?? EYE_BOB;
4560
+ const eyeOffset = options.eyeOffset ?? EYE_OFFSET;
4453
4561
  const root = new THREE.Group();
4454
4562
  const tilt = new THREE.Group();
4455
4563
  tilt.add(body);
@@ -4509,7 +4617,23 @@ var createPlayerAvatar = (THREE, options = {}) => {
4509
4617
  if (hand.th) hand.th.rotation.z = -hand.side * (0.32 + grip * 0.25);
4510
4618
  hand.hg.rotation.x = wristX;
4511
4619
  };
4620
+ const eyeSocket = new THREE.Object3D();
4621
+ eyeSocket.name = "eye";
4622
+ eyeSocket.position.set(BUILDER_EYE[0] + eyeOffset[0], BUILDER_EYE[1] + eyeOffset[1], BUILDER_EYE[2] + eyeOffset[2]);
4623
+ rig.headG.children[0].add(eyeSocket);
4624
+ const headHider = createHeadHider([rig.headG, ...rig.neck ? [rig.neck] : []]);
4625
+ const eyeLocal = new THREE.Vector3();
4626
+ let bob = 0;
4627
+ let hipWithoutBob = null;
4512
4628
  const legRestX = rig.legs.map((leg) => leg.g.position.x);
4629
+ const legLengths = (() => {
4630
+ const thigh = rig.legs[0].g;
4631
+ const shin = rig.legs[0].kn;
4632
+ root.updateMatrixWorld(true);
4633
+ const hipY = thigh.getWorldPosition(new THREE.Vector3()).y;
4634
+ const kneeY = shin.getWorldPosition(new THREE.Vector3()).y;
4635
+ return { hipBelowTrunk: HIP_HEIGHT - hipY, thigh: hipY - kneeY, shin: kneeY - new THREE.Box3().setFromObject(shin).min.y };
4636
+ })();
4513
4637
  const legSide = (index) => index === 0 ? 1 : -1;
4514
4638
  let cycles = 0;
4515
4639
  let time = 0;
@@ -4568,10 +4692,29 @@ var createPlayerAvatar = (THREE, options = {}) => {
4568
4692
  ...strain ? { legL: -0.5, legR: 0.4, kneeL: 0.55, kneeR: 0.08, drop: -0.05 } : {}
4569
4693
  };
4570
4694
  };
4695
+ const doorPose = (door) => {
4696
+ const { phase, progress } = door;
4697
+ const on = phase === "reach" ? progress : phase === "release" ? 1 - progress : 1;
4698
+ const swing = phase === "swing" ? progress : phase === "release" ? 1 : 0;
4699
+ const shake = phase === "rattle" ? Math.sin(time * 55) * 0.06 * (1 - progress) : 0;
4700
+ const drop = clamp((DOOR_SHOULDER - door.height) / DOOR_ARM, -0.9, 1);
4701
+ const low = clamp((0.75 - door.height) / 0.5, 0, 1);
4702
+ const raise = -Math.acos(clamp(drop, -1, 1)) * on - 0.35 * low * on;
4703
+ const elbow = door.pull ? -0.35 - 0.75 * swing : -0.95 + 0.75 * swing;
4704
+ return {
4705
+ armR: raise + shake,
4706
+ armRz: REST_ARM_Z * (1 - on) + 0.05 * on,
4707
+ elbR: -0.12 + (elbow + 0.12) * on,
4708
+ armLz: -REST_ARM_Z,
4709
+ elbL: -0.12,
4710
+ lean: ((door.pull ? -0.06 : 0.1) * swing + 0.35 * low) * on,
4711
+ headX: 0.06 * on + 0.2 * low * on
4712
+ };
4713
+ };
4571
4714
  const holdingPose = (gait, itemUse) => {
4572
- const bob = Math.sin(cycles * TWO_PI) * 0.05 * gait;
4715
+ const bob2 = Math.sin(cycles * TWO_PI) * 0.05 * gait;
4573
4716
  if (!itemUse) {
4574
- return { armL: -0.45 + bob, armLz: 0.22, elbL: -1.05 };
4717
+ return { armL: -0.45 + bob2, armLz: 0.22, elbL: -1.05 };
4575
4718
  }
4576
4719
  const { id, position, rotation } = itemUse;
4577
4720
  if (id === "drink" || id === "sip" || id === "chug") {
@@ -4584,7 +4727,7 @@ var createPlayerAvatar = (THREE, options = {}) => {
4584
4727
  headX: -0.26 * tilt3
4585
4728
  };
4586
4729
  }
4587
- if (id === "open" || id === "close") {
4730
+ if (id === "open" || id === "close" || itemUse.gripL !== void 0) {
4588
4731
  const reach = clamp((0.22 - position[0]) / 0.12, 0, 1);
4589
4732
  return {
4590
4733
  armL: -0.45 - 0.35 * reach,
@@ -4683,6 +4826,31 @@ var createPlayerAvatar = (THREE, options = {}) => {
4683
4826
  drop: Math.abs(Math.sin(ph)) * WALK_BOB * gait
4684
4827
  };
4685
4828
  };
4829
+ const sitPose = (hip) => {
4830
+ const knee = hip - legLengths.hipBelowTrunk - legLengths.thigh * Math.cos(SIT.thigh);
4831
+ const shinFromVertical = Math.acos(clamp(knee / legLengths.shin, 0, 1));
4832
+ const bend = -SIT.thigh - shinFromVertical;
4833
+ return {
4834
+ legL: SIT.thigh,
4835
+ legR: SIT.thigh,
4836
+ kneeL: bend,
4837
+ kneeR: bend,
4838
+ lean: SIT.lean,
4839
+ drop: hip - HIP_HEIGHT,
4840
+ armR: SIT.arm,
4841
+ armL: SIT.arm,
4842
+ armRz: SIT.armZ,
4843
+ armLz: -SIT.armZ,
4844
+ elbR: SIT.elbow,
4845
+ elbL: SIT.elbow
4846
+ };
4847
+ };
4848
+ const liePose = () => ({ legL: 0, legR: 0, kneeL: LIE.knee, kneeR: LIE.knee, lean: 0, drop: 0, armR: 0, armL: 0, armRz: LIE.armZ, armLz: -LIE.armZ, elbR: LIE.elbow, elbL: LIE.elbow });
4849
+ const layBody = (hip) => {
4850
+ tilt.rotation.set(-Math.PI / 2, 0, 0, "XYZ");
4851
+ const pivot = new THREE.Vector3(0, HIP_HEIGHT, 0).applyEuler(tilt.rotation);
4852
+ tilt.position.set(0, hip, 0).sub(pivot);
4853
+ };
4686
4854
  const placeBody = (progress) => {
4687
4855
  const fk = progress * progress * (3 - 2 * progress);
4688
4856
  const flip = fract(cycles * 1.7) >= 0.5 ? 1 : 0;
@@ -4743,13 +4911,27 @@ var createPlayerAvatar = (THREE, options = {}) => {
4743
4911
  profile.bob = (running ? RUN_BOB : WALK_BOB) * gait;
4744
4912
  time = cycles / (walkCadence(Math.max(speed, 0)) * (running ? 1.3 : 1));
4745
4913
  animator.animate(body, time, { speed });
4914
+ bob = rig.fig.position.y - rig.dropBase;
4746
4915
  fall = fallSeconds <= 0 ? down ? 1 : 0 : clamp(fall + (down ? dt : -dt) / fallSeconds, 0, 1);
4747
4916
  resetLegYaw();
4748
- if (down) animator.setPose(rig, downPose());
4749
- else {
4750
- if (running) animator.setPose(rig, runningPose(gait, Boolean(state.armed || state.holding || state.itemUse)));
4751
- if (state.crouch) animator.setPose(rig, crouchPose(gait));
4917
+ const seat = down ? null : state.seat ?? null;
4918
+ if (down) {
4919
+ animator.setPose(rig, downPose());
4920
+ bob = rig.fig.position.y - rig.dropBase;
4921
+ } else if (seat) {
4922
+ animator.setPose(rig, seat.pose === "lie" ? liePose() : sitPose(seat.hip));
4923
+ bob = 0;
4924
+ } else {
4925
+ if (running) {
4926
+ animator.setPose(rig, runningPose(gait, Boolean(state.armed || state.holding || state.itemUse)));
4927
+ bob = rig.fig.position.y - rig.dropBase;
4928
+ }
4929
+ if (state.crouch) {
4930
+ animator.setPose(rig, crouchPose(gait));
4931
+ bob = rig.fig.position.y - rig.dropBase - CROUCH.drop;
4932
+ }
4752
4933
  if (state.push) animator.setPose(rig, pushingPose(state.push));
4934
+ else if (state.door) animator.setPose(rig, doorPose(state.door));
4753
4935
  else if (state.armed) animator.setPose(rig, armed(state.pitch ?? 0, gait, state.hip ?? 0));
4754
4936
  else if (state.holding || state.itemUse) animator.setPose(rig, holdingPose(gait, state.itemUse));
4755
4937
  else if (!running) {
@@ -4761,20 +4943,33 @@ var createPlayerAvatar = (THREE, options = {}) => {
4761
4943
  });
4762
4944
  }
4763
4945
  }
4764
- placeBody(fall);
4765
- stepClip(dt, down, Boolean(state.crouch));
4766
- if (smooth) animator.cascade(rig, run && !run.clip.loop ? dt * clipCascade : dt);
4946
+ if (seat?.pose === "lie") layBody(seat.hip);
4947
+ else placeBody(fall);
4948
+ stepClip(dt, down, Boolean(state.crouch || seat));
4949
+ const cascadeDt = run && !run.clip.loop ? dt * clipCascade : dt;
4950
+ const targetWithoutBob = rig.fig.position.y - bob;
4951
+ if (smooth) animator.cascade(rig, cascadeDt);
4952
+ hipWithoutBob = smooth && hipWithoutBob !== null ? stepCascade2(hipWithoutBob, targetWithoutBob, cascadeDt, "hip") : targetWithoutBob;
4953
+ bob = rig.fig.position.y - hipWithoutBob;
4767
4954
  const blow = run?.clip.events?.find((event) => (event.type === "impact" || event.type === "grip") && event.data?.kind)?.data?.kind;
4768
4955
  const ph = cycles * TWO_PI;
4769
4956
  hands.forEach((hand) => {
4770
4957
  const isRight = hand.side < 0;
4771
4958
  const holdRight = isRight && (!!state.holding || !!state.itemUse);
4772
- const holdLeft = !isRight && (state.itemUse?.id === "open" || state.itemUse?.id === "close");
4773
- const grip = blow === "punch" ? 1.4 : blow === "shove" ? 0.15 : state.armed || holdRight || holdLeft ? 1.22 : running ? 0.85 : 0.12;
4959
+ const holdLeft = !isRight && (state.itemUse?.id === "open" || state.itemUse?.id === "close" || state.itemUse?.gripL !== void 0);
4960
+ const grip = !isRight && state.itemUse?.gripL !== void 0 ? 1.22 * state.itemUse.gripL : blow === "punch" ? 1.4 : blow === "shove" ? 0.15 : state.armed || holdRight || holdLeft ? 1.22 : running ? 0.85 : 0.12;
4774
4961
  const wristSwing = holdRight || state.armed || running ? 0 : -Math.min(1, gait) * Math.sin(ph - 0.6) * 0.12 * hand.side;
4775
4962
  setHandGrip(hand, grip, wristSwing);
4776
4963
  });
4964
+ root.updateMatrixWorld(true);
4965
+ root.worldToLocal(eyeSocket.getWorldPosition(eyeLocal));
4966
+ eyeLocal.y -= (1 - eyeBob) * bob;
4777
4967
  },
4968
+ eye(out = new THREE.Vector3()) {
4969
+ root.updateWorldMatrix(true, false);
4970
+ return root.localToWorld(out.copy(eyeLocal));
4971
+ },
4972
+ setView: (mode, camera) => headHider.setView(mode, camera),
4778
4973
  play(clip, playOptions = {}) {
4779
4974
  const found = typeof clip === "string" ? clips.get(clip) : clip;
4780
4975
  if (!found) throw new Error(`Unknown pose clip: ${String(clip)}`);
@@ -4786,10 +4981,11 @@ var createPlayerAvatar = (THREE, options = {}) => {
4786
4981
  if (run) run.target = 0;
4787
4982
  },
4788
4983
  hand: (side) => sockets[side],
4789
- reach(targets) {
4984
+ shoulder: (side) => armOf[side].g,
4985
+ reach(targets, view) {
4790
4986
  if (run || fall > 0) return;
4791
4987
  root.updateMatrixWorld(true);
4792
- armReach.apply(targets);
4988
+ armReach.apply(targets, view);
4793
4989
  },
4794
4990
  on(_type, listener) {
4795
4991
  listeners.add(listener);
@@ -4827,7 +5023,9 @@ var first_person_arms_default = {
4827
5023
  shoulder: [0.16, -0.36, 0.18],
4828
5024
  upper: 0.32,
4829
5025
  fore: 0.3,
4830
- reachShift: 0.35,
5026
+ reachShift: 0.42,
5027
+ reachBend: [1.35, 0.95],
5028
+ elbowView: [[0.96, -0.26, -0.06], null],
4831
5029
  wrist: 0.07,
4832
5030
  elbowPole: [0.45, -1, 0.2],
4833
5031
  shoulderLeft: [-0.07, -0.46, 0.18],
@@ -4854,22 +5052,14 @@ var first_person_arms_default = {
4854
5052
  };
4855
5053
 
4856
5054
  // src/first-person/hand.ts
4857
- var FINGERS = [
4858
- { z: -0.03, lengths: [0.034, 0.024, 0.02], width: 0.016 },
4859
- { z: -0.0105, lengths: [0.038, 0.027, 0.022], width: 0.0165 },
4860
- { z: 9e-3, lengths: [0.035, 0.025, 0.02], width: 0.0155 },
4861
- { z: 0.027, lengths: [0.029, 0.02, 0.018], width: 0.0135 }
4862
- ];
4863
- var PALM = { length: 0.09, thick: 0.026, width: 0.09, wristWidth: 0.066 };
4864
- var FINGER_THICK = 0.0135;
4865
- var CURL = { near: 1.05, mid: 1.25, far: 0.85, spread: 0.06 };
4866
- var THUMB = { base: [5e-3, -6e-3, -0.044], near: 0.034, far: 0.028, width: 0.019 };
5055
+ var PALM = { radius: 0.048, scale: [0.6, 1.05, 1], down: 0.035 };
5056
+ var CURL = { closed: 1.25, near: 0.9, far: 1.1 };
5057
+ var THUMB = { open: 0.32, closed: 0.25 };
4867
5058
  var createHandMesh = (THREE, skin, wristSkin = skin) => {
4868
5059
  const root = new THREE.Group();
4869
5060
  root.name = "first-person-hand";
4870
5061
  const geometries = [];
4871
- const box = (parent, sx, sy, sz, x, y, z, material = skin) => {
4872
- const geometry = new THREE.BoxGeometry(sx, sy, sz);
5062
+ const add = (parent, geometry, material, x = 0, y = 0, z = 0) => {
4873
5063
  geometries.push(geometry);
4874
5064
  const mesh = new THREE.Mesh(geometry, material);
4875
5065
  mesh.position.set(x, y, z);
@@ -4877,64 +5067,26 @@ var createHandMesh = (THREE, skin, wristSkin = skin) => {
4877
5067
  parent.add(mesh);
4878
5068
  return mesh;
4879
5069
  };
4880
- const palmGeometry = new THREE.BoxGeometry(PALM.length, PALM.thick, PALM.width);
4881
- const palmPosition = palmGeometry.getAttribute("position");
4882
- for (let i = 0; i < palmPosition.count; i++) {
4883
- if (palmPosition.getX(i) < 0) palmPosition.setZ(i, palmPosition.getZ(i) * PALM.wristWidth / PALM.width);
4884
- }
4885
- palmGeometry.computeVertexNormals();
4886
- geometries.push(palmGeometry);
4887
- const palm = new THREE.Mesh(palmGeometry, skin);
4888
- palm.frustumCulled = false;
4889
- root.add(palm);
4890
- const wristGeometry = new THREE.CylinderGeometry(0.03, 0.033, 0.05, 8);
5070
+ const group = new THREE.Group();
5071
+ group.quaternion.setFromRotationMatrix(new THREE.Matrix4().makeBasis(new THREE.Vector3(0, -1, 0), new THREE.Vector3(-1, 0, 0), new THREE.Vector3(0, 0, -1)));
5072
+ group.position.set(-PALM.down, 0, 0);
5073
+ root.add(group);
5074
+ add(group, new THREE.SphereGeometry(PALM.radius, 14, 10), skin, 0, -PALM.down, 0).scale.set(...PALM.scale);
5075
+ const built = buildHand({ THREE, add, hg: group, handM: skin, s: -1, detailed: true });
5076
+ const wristGeometry = new THREE.CylinderGeometry(0.03, 0.033, 0.05, 10);
4891
5077
  wristGeometry.rotateZ(Math.PI / 2);
4892
- geometries.push(wristGeometry);
4893
- const wrist = new THREE.Mesh(wristGeometry, wristSkin);
4894
- wrist.position.set(-PALM.length / 2 - 0.018, 0, 0);
4895
- wrist.frustumCulled = false;
4896
- root.add(wrist);
4897
- const joints = [];
4898
- FINGERS.forEach((finger, index) => {
4899
- const segments = [];
4900
- let parent = root;
4901
- let at = PALM.length / 2;
4902
- finger.lengths.forEach((length, k) => {
4903
- const group = new THREE.Group();
4904
- group.position.set(at, k === 0 ? 2e-3 : 0, k === 0 ? finger.z : 0);
4905
- const taper = 1 - k * 0.07;
4906
- box(group, length, FINGER_THICK * taper, finger.width * taper, length / 2, 0, 0);
4907
- parent.add(group);
4908
- segments.push(group);
4909
- parent = group;
4910
- at = length;
4911
- });
4912
- joints.push({ segments, weight: 1 + index * (CURL.spread / CURL.near) });
4913
- });
4914
- const thumbNear = new THREE.Group();
4915
- thumbNear.rotation.order = "YZX";
4916
- thumbNear.position.set(...THUMB.base);
4917
- box(thumbNear, THUMB.near, FINGER_THICK * 1.1, THUMB.width, THUMB.near / 2, 0, 0);
4918
- const thumbFar = new THREE.Group();
4919
- thumbFar.position.set(THUMB.near, 0, 0);
4920
- box(thumbFar, THUMB.far, FINGER_THICK, THUMB.width * 0.9, THUMB.far / 2, 0, 0);
4921
- thumbNear.add(thumbFar);
4922
- root.add(thumbNear);
5078
+ add(root, wristGeometry, wristSkin, -PALM.down - 0.03, 0, 0);
4923
5079
  let lastGrip = 0;
4924
5080
  let indexCurl;
4925
- const curl = [CURL.near, CURL.mid, CURL.far];
4926
- const applyIndex = () => {
4927
- const finger = joints[0];
4928
- if (!finger) return;
4929
- const k = indexCurl === void 0 ? lastGrip : Math.min(1, Math.max(0, indexCurl));
4930
- finger.segments.forEach((segment, i) => segment.rotation.z = -curl[i] * k * finger.weight);
5081
+ const curlFinger = (finger, k) => {
5082
+ built.fingers[finger * 2].rotation.z = k * CURL.closed * CURL.near;
5083
+ built.fingers[finger * 2 + 1].rotation.z = k * CURL.closed * CURL.far;
4931
5084
  };
5085
+ const applyIndex = () => curlFinger(0, indexCurl === void 0 ? lastGrip : Math.min(1, Math.max(0, indexCurl)));
4932
5086
  const setGrip = (grip) => {
4933
- const k = Math.min(1, Math.max(0, grip));
4934
- lastGrip = k;
4935
- for (const joint of joints) joint.segments.forEach((segment, i) => segment.rotation.z = -curl[i] * k * joint.weight);
4936
- thumbNear.rotation.set(0, 0.55 - 1.45 * k, -0.1 - 0.75 * k);
4937
- thumbFar.rotation.z = -0.1 - 0.7 * k;
5087
+ lastGrip = Math.min(1, Math.max(0, grip));
5088
+ for (let finger = 1; finger < 4; finger++) curlFinger(finger, lastGrip);
5089
+ if (built.th) built.th.rotation.z = THUMB.open + lastGrip * THUMB.closed;
4938
5090
  applyIndex();
4939
5091
  };
4940
5092
  const setIndex = (value) => {
@@ -4956,6 +5108,8 @@ var createHandMesh = (THREE, skin, wristSkin = skin) => {
4956
5108
  var DEFAULT_BLEND = 0.18;
4957
5109
  var ELBOW_SEGMENTS = 8;
4958
5110
  var REACH_SLACK = 0.97;
5111
+ var ELBOW_VIEW_MARGIN = 0.86;
5112
+ var ELBOW_VIEW_PASSES = 4;
4959
5113
  var CONTACT_PROBE = 1.4;
4960
5114
  var CONTACT_MIN = 0.12;
4961
5115
  var CONTACT_MARGIN = 5e-3;
@@ -5104,6 +5258,11 @@ function createFirstPersonArms(THREE, options = {}) {
5104
5258
  const shoulderOf = (sign, out) => sign === -1 && rig.shoulderLeft ? out.set(...rig.shoulderLeft) : out.set(rig.shoulder[0] * sign, rig.shoulder[1], rig.shoulder[2]);
5105
5259
  const poleOf = (sign) => sign === -1 && rig.elbowPoleLeft ? new THREE.Vector3(...rig.elbowPoleLeft) : new THREE.Vector3(rig.elbowPole[0] * sign, rig.elbowPole[1], rig.elbowPole[2]);
5106
5260
  const kneePole = new THREE.Vector3(...rig.kneePole);
5261
+ const reachOf = (sign) => {
5262
+ const bend = rig.reachBend?.[sign === 1 ? 0 : 1];
5263
+ if (bend === void 0) return (rig.upper + rig.fore) * REACH_SLACK;
5264
+ return Math.sqrt(rig.upper * rig.upper + rig.fore * rig.fore + 2 * rig.upper * rig.fore * Math.cos(bend));
5265
+ };
5107
5266
  const hip = new THREE.Vector3(...rig.hip);
5108
5267
  const euler = new THREE.Euler(0, 0, 0, "YZX");
5109
5268
  const quaternionOf = (r) => new THREE.Quaternion().setFromEuler(euler.set(r[0], r[1], r[2], "YZX"));
@@ -5193,15 +5352,45 @@ function createFirstPersonArms(THREE, options = {}) {
5193
5352
  out.scale = base.scale;
5194
5353
  return out;
5195
5354
  };
5196
- const drawArm = (arm, pose) => {
5355
+ const elbowAt = new THREE.Vector3();
5356
+ const viewOf = (camera) => {
5357
+ const lens = camera;
5358
+ if (!lens.isPerspectiveCamera) return null;
5359
+ const tanV = Math.tan(lens.fov * Math.PI / 360);
5360
+ return { tanV, tanH: tanV * lens.aspect };
5361
+ };
5362
+ const placeElbowInView = (sign, view) => {
5363
+ const way = rig.elbowView?.[sign === 1 ? 0 : 1];
5364
+ if (!way) return false;
5365
+ elbowAt.set(way[0], way[1], way[2]).setLength(rig.fore).add(wristAt);
5366
+ for (let pass = 0; pass < ELBOW_VIEW_PASSES; pass++) {
5367
+ const depth = Math.max(0.05, -elbowAt.z);
5368
+ const high = view.tanV * ELBOW_VIEW_MARGIN * depth;
5369
+ const wide = view.tanH * ELBOW_VIEW_MARGIN * depth;
5370
+ elbowAt.y = Math.max(elbowAt.y, -high);
5371
+ elbowAt.x = Math.min(wide, Math.max(-wide, elbowAt.x));
5372
+ elbowAt.sub(wristAt).setLength(rig.fore).add(wristAt);
5373
+ }
5374
+ shoulder.sub(elbowAt).setLength(rig.upper).add(elbowAt);
5375
+ return true;
5376
+ };
5377
+ const drawArm = (arm, pose, view) => {
5197
5378
  copyPose(arm.drawn, pose);
5198
5379
  const scale = pose.scale;
5199
5380
  fingers.set(1, 0, 0).applyQuaternion(pose.q);
5200
5381
  wristAt.copy(pose.p).addScaledVector(fingers, -rig.wrist * scale);
5201
5382
  shoulderOf(arm.sign, shoulder).add(bobOffset);
5202
- const excess = shoulder.distanceTo(wristAt) - (rig.upper + rig.fore) * REACH_SLACK;
5383
+ if (view && placeElbowInView(arm.sign, view)) {
5384
+ solveTwoBone(shoulder, wristAt, rig.upper, rig.fore, towards.copy(elbowAt).sub(shoulder).add(elbowAt).sub(wristAt), bone);
5385
+ finishArm(arm, pose, scale);
5386
+ return;
5387
+ }
5388
+ const excess = shoulder.distanceTo(wristAt) - reachOf(arm.sign);
5203
5389
  if (excess > 0 && rig.reachShift > 0) shoulder.addScaledVector(towards.subVectors(wristAt, shoulder).normalize(), Math.min(excess, rig.reachShift));
5204
5390
  solveTwoBone(shoulder, wristAt, rig.upper, rig.fore, poleOf(arm.sign), bone);
5391
+ finishArm(arm, pose, scale);
5392
+ };
5393
+ const finishArm = (arm, pose, scale) => {
5205
5394
  palmAt.copy(bone.end).addScaledVector(fingers, rig.wrist * scale);
5206
5395
  arm.drawn.p.copy(palmAt);
5207
5396
  arm.anchor.position.copy(palmAt);
@@ -5313,7 +5502,7 @@ function createFirstPersonArms(THREE, options = {}) {
5313
5502
  dropped.p.y -= AWAY_DROP * ease(arm.away);
5314
5503
  pose = dropped;
5315
5504
  }
5316
- drawArm(arm, pose);
5505
+ drawArm(arm, pose, mode === "target" && targets?.elbows !== "body" ? viewOf(state.camera) : null);
5317
5506
  const shown = arm.away < AWAY_HIDDEN;
5318
5507
  for (const part of [arm.anchor, arm.upper, arm.fore, arm.cuff, arm.elbow]) part.visible = shown;
5319
5508
  }
@@ -5361,7 +5550,7 @@ var impactOf = (clip) => clip.events?.find((event) => event.type === "impact")?.
5361
5550
  var isPunch = (clip) => clip.events?.some((event) => event.type === "impact" && event.data?.kind === "punch") ?? false;
5362
5551
  var GUARD_SPEED = 0.3;
5363
5552
  var toRoomAvatar = (avatar, options = {}) => {
5364
- const arms = options.firstPerson === false ? null : createFirstPersonArms(avatar.three, {
5553
+ const arms = options.firstPerson === false || options.firstPerson === void 0 ? null : createFirstPersonArms(avatar.three, {
5365
5554
  colors: { skin: avatar.colors.skin, sleeve: avatar.colors.longSleeves ? avatar.colors.shirt : avatar.colors.skin, trouser: avatar.colors.trousers, shoe: avatar.colors.shoes, ...options.firstPerson?.colors },
5366
5555
  clips: options.firstPersonClips ?? FIRST_PERSON_CLIPS,
5367
5556
  hideWhenIdle: true,
@@ -5382,7 +5571,7 @@ var toRoomAvatar = (avatar, options = {}) => {
5382
5571
  let guarding = false;
5383
5572
  const stepGuard = (dt, state) => {
5384
5573
  if (!guard) return;
5385
- const free = state.speed < GUARD_SPEED && !state.down && !state.armed && !state.holding && !state.push && !state.itemUse && !state.running;
5574
+ const free = state.speed < GUARD_SPEED && !state.down && !state.armed && !state.holding && !state.push && !state.door && !state.itemUse && !state.running;
5386
5575
  if (strikeLeft > 0) {
5387
5576
  strikeLeft -= dt;
5388
5577
  if (strikeLeft <= 0 && guardLeft > 0 && free) {
@@ -5401,7 +5590,8 @@ var toRoomAvatar = (avatar, options = {}) => {
5401
5590
  root: avatar.root,
5402
5591
  avatar,
5403
5592
  arms,
5404
- ...arms ? { firstPerson: { root: arms.root, update: (dt, state) => arms.update(dt, state) } } : {},
5593
+ eye: (out) => avatar.eye(out),
5594
+ setView: (mode, camera) => avatar.setView(mode, camera),
5405
5595
  update: (dt, state) => {
5406
5596
  stepGuard(dt, state);
5407
5597
  avatar.update(dt, {
@@ -5413,11 +5603,14 @@ var toRoomAvatar = (avatar, options = {}) => {
5413
5603
  pitch: -state.pitch,
5414
5604
  ...state.holding !== void 0 ? { holding: state.holding } : {},
5415
5605
  ...state.itemUse !== void 0 ? { itemUse: state.itemUse } : {},
5416
- ...state.push !== void 0 ? { push: state.push } : {}
5606
+ ...state.push !== void 0 ? { push: state.push } : {},
5607
+ ...state.seat !== void 0 ? { seat: state.seat } : {},
5608
+ ...state.door !== void 0 ? { door: state.door } : {}
5417
5609
  });
5418
5610
  },
5419
5611
  hand: (side) => avatar.hand(side),
5420
- reach: (targets) => avatar.reach(targets),
5612
+ shoulder: (side) => avatar.shoulder(side),
5613
+ reach: (targets, view) => avatar.reach(targets, view),
5421
5614
  strike: (clipId, strikeOptions) => {
5422
5615
  const clip = strikes.get(clipId);
5423
5616
  if (!clip) return null;