@adatechnology/game-characters-three 0.5.1 → 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
 
@@ -4235,12 +4247,272 @@ var player_avatar_default = {
4235
4247
  shirtColor: "3d5a73"
4236
4248
  };
4237
4249
 
4250
+ // src/first-person/ik.ts
4251
+ var solveTwoBone = (root, target, upper, lower, pole, out) => {
4252
+ const line = out.line.copy(target).sub(root);
4253
+ let distance2 = line.length();
4254
+ if (distance2 < 1e-6) line.set(0, 0, -1);
4255
+ else line.divideScalar(distance2);
4256
+ const min = Math.abs(upper - lower) + 1e-4;
4257
+ const max = upper + lower - 1e-4;
4258
+ out.clamped = distance2 > max || distance2 < min;
4259
+ distance2 = Math.min(max, Math.max(min, distance2));
4260
+ const along = (distance2 * distance2 + upper * upper - lower * lower) / (2 * distance2);
4261
+ const across = Math.sqrt(Math.max(0, upper * upper - along * along));
4262
+ const side = out.side.copy(pole);
4263
+ side.addScaledVector(line, -side.dot(line));
4264
+ if (side.lengthSq() < 1e-8) side.set(0, 1, 0).addScaledVector(line, -line.y);
4265
+ if (side.lengthSq() < 1e-8) side.set(1, 0, 0);
4266
+ side.normalize();
4267
+ out.joint.copy(root).addScaledVector(line, along).addScaledVector(side, across);
4268
+ out.end.copy(root).addScaledVector(line, distance2);
4269
+ return out;
4270
+ };
4271
+ var createTwoBone = (THREE) => ({ joint: new THREE.Vector3(), end: new THREE.Vector3(), clamped: false, line: new THREE.Vector3(), side: new THREE.Vector3() });
4272
+
4273
+ // src/player/arm-reach.ts
4274
+ var ARM_REACH = {
4275
+ /** The elbow never straightens past this bend (a locked elbow reads as a stick). */
4276
+ elbowMin: 0.12,
4277
+ /** Full flexion of the elbow. */
4278
+ elbowMax: 2.5,
4279
+ /** The wrist bends (flexion, extension, deviation) up to this angle from the line of the forearm (flexion reaches about 80 degrees). */
4280
+ wristBend: 1.3,
4281
+ /** The forearm turns (pronation and supination) up to this angle about its own axis. */
4282
+ wristTwist: 1.6,
4283
+ /** The most the chest turns to bring the support shoulder toward a far hand. */
4284
+ chestTurn: 0.6,
4285
+ /** The most a shoulder blade slides toward a far hand (protraction and elevation together). */
4286
+ protraction: 0.11,
4287
+ /** A hand is within reach below this fraction of the arm's length. */
4288
+ slack: 0.96,
4289
+ /** Where the elbow points: down and out from the body, a little back (body space, x outward). */
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],
4293
+ /** From the wrist joint to the middle of the palm, metres of the body. */
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
4307
+ };
4308
+ var createArmReach = (THREE, rig, hands) => {
4309
+ const arms = {
4310
+ R: { g: rig.arms[1].g, el: rig.arms[1].el, hand: hands.R, outward: -1 },
4311
+ L: { g: rig.arms[0].g, el: rig.arms[0].el, hand: hands.L, outward: 1 }
4312
+ };
4313
+ const upper = rig.upper;
4314
+ const handBasis = {
4315
+ R: new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().makeBasis(new THREE.Vector3(0, -1, 0), new THREE.Vector3(-1, 0, 0), new THREE.Vector3(0, 0, -1))).invert(),
4316
+ L: new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().makeBasis(new THREE.Vector3(0, -1, 0), new THREE.Vector3(1, 0, 0), new THREE.Vector3(0, 0, 1))).invert()
4317
+ };
4318
+ const saved = {
4319
+ twist: 0,
4320
+ arms: { R: { position: new THREE.Vector3(), g: new THREE.Quaternion(), el: new THREE.Quaternion(), hand: new THREE.Quaternion() }, L: { position: new THREE.Vector3(), g: new THREE.Quaternion(), el: new THREE.Quaternion(), hand: new THREE.Quaternion() } }
4321
+ };
4322
+ let applied = false;
4323
+ const bone = createTwoBone(THREE);
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() };
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() };
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;
4329
+ const yAxis = new THREE.Vector3(0, 1, 0);
4330
+ const lengthsOf = (arm) => {
4331
+ const scale = arm.g.getWorldScale(v.a).y;
4332
+ return { upper: arm.el.position.length() * scale, fore: arm.hand.position.length() * scale, palm: ARM_REACH.palm * scale };
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
+ };
4338
+ const wristOf = (side, pose, palm) => {
4339
+ q.hand.set(...pose.quaternion).multiply(handBasis[side]);
4340
+ v.palm.set(...pose.position);
4341
+ v.wrist.copy(v.palm).addScaledVector(v.a.set(0, -1, 0).applyQuaternion(q.hand), -palm);
4342
+ };
4343
+ const turnChest = (pose) => {
4344
+ const { upper: up, fore, palm } = lengthsOf(arms.L);
4345
+ wristOf("L", pose, palm);
4346
+ const excess = arms.L.g.getWorldPosition(v.shoulder).distanceTo(v.wrist) - limitOf("L", up, fore);
4347
+ if (excess <= 0) return;
4348
+ const chestRadius = Math.hypot(arms.L.g.position.x, arms.L.g.position.z) * arms.L.g.getWorldScale(v.a).y;
4349
+ upper.rotation.y -= Math.min(ARM_REACH.chestTurn, excess / Math.max(0.05, chestRadius));
4350
+ upper.updateMatrixWorld(true);
4351
+ };
4352
+ const protract = (arm, limit) => {
4353
+ const excess = arm.g.getWorldPosition(v.shoulder).distanceTo(v.wrist) - limit;
4354
+ if (excess <= 0) return;
4355
+ const parent = arm.g.parent;
4356
+ v.b.copy(v.wrist).sub(v.shoulder).normalize().multiplyScalar(Math.min(excess, ARM_REACH.protraction)).add(v.shoulder);
4357
+ arm.g.position.copy(parent.worldToLocal(v.b));
4358
+ arm.g.updateMatrixWorld(true);
4359
+ };
4360
+ const limitWrist = (local) => {
4361
+ const projection = v.c.set(local.x, local.y, local.z).projectOnVector(yAxis);
4362
+ q.twist.set(projection.x, projection.y, projection.z, local.w).normalize();
4363
+ const twistAngle = 2 * Math.atan2(q.twist.y, q.twist.w);
4364
+ const swing = q.swing.copy(local).multiply(q.twist.invert());
4365
+ const swingAngle = 2 * Math.acos(Math.min(1, Math.abs(swing.w)));
4366
+ if (swingAngle > ARM_REACH.wristBend) swing.slerp(q.rest.identity(), 1 - ARM_REACH.wristBend / swingAngle);
4367
+ const clampedTwist = Math.max(-ARM_REACH.wristTwist, Math.min(ARM_REACH.wristTwist, Math.atan2(Math.sin(twistAngle), Math.cos(twistAngle))));
4368
+ return local.copy(swing).multiply(q.twist.setFromAxisAngle(yAxis, clampedTwist));
4369
+ };
4370
+ const reach = (side, pose) => {
4371
+ const arm = arms[side];
4372
+ const { upper: up, fore, palm } = lengthsOf(arm);
4373
+ wristOf(side, pose, palm);
4374
+ protract(arm, limitOf(side, up, fore));
4375
+ arm.g.getWorldPosition(v.shoulder);
4376
+ upper.getWorldQuaternion(q.body);
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;
4381
+ const least = Math.sqrt(up * up + fore * fore + 2 * up * fore * Math.cos(ARM_REACH.elbowMax));
4382
+ const line = v.c.copy(v.wrist).sub(v.shoulder);
4383
+ const distance2 = Math.min(most, Math.max(least, line.length()));
4384
+ v.target.copy(v.shoulder).addScaledVector(line.normalize(), distance2);
4385
+ solveTwoBone(v.shoulder, v.target, up, fore, v.pole, bone);
4386
+ const along = v.a.copy(bone.joint).sub(v.shoulder).normalize();
4387
+ const forearm = v.b.copy(bone.end).sub(bone.joint).normalize();
4388
+ const fold = v.c.copy(forearm).addScaledVector(along, -forearm.dot(along));
4389
+ if (fold.lengthSq() < 1e-8) fold.copy(v.pole).addScaledVector(along, -v.pole.dot(along)).negate();
4390
+ fold.normalize();
4391
+ const yWorld = v.elbow.copy(along).negate();
4392
+ basis.makeBasis(v.pole.crossVectors(yWorld, fold), yWorld, fold);
4393
+ arm.g.parent.getWorldQuaternion(q.parent);
4394
+ arm.g.quaternion.setFromRotationMatrix(basis).premultiply(q.parent.invert());
4395
+ arm.el.rotation.set(-Math.acos(Math.max(-1, Math.min(1, along.dot(forearm)))), 0, 0);
4396
+ arm.g.updateMatrixWorld(true);
4397
+ const reached = arm.hand.getWorldPosition(v.a).sub(v.shoulder).normalize();
4398
+ q.swing.setFromUnitVectors(reached, v.b.copy(v.target).sub(v.shoulder).normalize());
4399
+ arm.g.parent.getWorldQuaternion(q.parent);
4400
+ arm.g.quaternion.premultiply(q.parent).premultiply(q.swing).premultiply(q.parent.invert());
4401
+ arm.g.updateMatrixWorld(true);
4402
+ arm.el.getWorldQuaternion(q.parent);
4403
+ arm.hand.quaternion.copy(limitWrist(q.local.copy(q.parent.invert()).multiply(q.hand)));
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
+ };
4419
+ const save = () => {
4420
+ saved.twist = upper.rotation.y;
4421
+ for (const side of ["R", "L"]) {
4422
+ const arm = arms[side];
4423
+ const into = saved.arms[side];
4424
+ into.position.copy(arm.g.position);
4425
+ into.g.copy(arm.g.quaternion);
4426
+ into.el.copy(arm.el.quaternion);
4427
+ into.hand.copy(arm.hand.quaternion);
4428
+ }
4429
+ };
4430
+ return {
4431
+ apply(targets, view) {
4432
+ if (!targets || !targets.R && !targets.L) return;
4433
+ save();
4434
+ applied = true;
4435
+ upper.updateWorldMatrix(true, true);
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;
4449
+ },
4450
+ restore() {
4451
+ if (!applied) return;
4452
+ applied = false;
4453
+ upper.rotation.y = saved.twist;
4454
+ for (const side of ["R", "L"]) {
4455
+ const arm = arms[side];
4456
+ const from = saved.arms[side];
4457
+ arm.g.position.copy(from.position);
4458
+ arm.g.quaternion.copy(from.g);
4459
+ arm.el.quaternion.copy(from.el);
4460
+ arm.hand.quaternion.copy(from.hand);
4461
+ }
4462
+ }
4463
+ };
4464
+ };
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
+
4238
4507
  // src/player/avatar.ts
4239
4508
  var TWO_PI = Math.PI * 2;
4240
4509
  var MAX_GAIT = 1.4;
4241
4510
  var CLIP_BLEND = 0.12;
4242
4511
  var CLIP_CASCADE = 5;
4243
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;
4244
4516
  var HIP_HEIGHT = 0.95;
4245
4517
  var WALK_ARM = 0.28;
4246
4518
  var WALK_BOB = 0.035;
@@ -4250,7 +4522,11 @@ var RUN_BOB = 0.05;
4250
4522
  var REST_ARM_Z = 0.18;
4251
4523
  var CROUCH = { thigh: -0.95, thighWalk: 0.3, shin: 1.9, lean: 0.25, drop: -0.35 };
4252
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 };
4253
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;
4254
4530
  var HAND_OFFSET = { x: 0, y: -0.33, z: 0.04 };
4255
4531
  var hexOf = (value) => typeof value === "string" ? Number.parseInt(value, 16) : value;
4256
4532
  var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
@@ -4280,6 +4556,8 @@ var createPlayerAvatar = (THREE, options = {}) => {
4280
4556
  const fallSeconds = options.fallSeconds ?? FALL_SECONDS2;
4281
4557
  const smooth = options.cascade ?? true;
4282
4558
  const clipCascade = options.clipCascade ?? CLIP_CASCADE;
4559
+ const eyeBob = options.eyeBob ?? EYE_BOB;
4560
+ const eyeOffset = options.eyeOffset ?? EYE_OFFSET;
4283
4561
  const root = new THREE.Group();
4284
4562
  const tilt = new THREE.Group();
4285
4563
  tilt.add(body);
@@ -4331,6 +4609,7 @@ var createPlayerAvatar = (THREE, options = {}) => {
4331
4609
  const built = buildHand({ THREE, add: addMesh, hg, handM: handMaterial, s: side, detailed: true });
4332
4610
  hands.push({ side, hg, fingers: built.fingers, th: built.th });
4333
4611
  });
4612
+ const armReach = createArmReach(THREE, rig, { R: hands.find((hand) => hand.side < 0).hg, L: hands.find((hand) => hand.side > 0).hg });
4334
4613
  const setHandGrip = (hand, grip, wristX = 0) => {
4335
4614
  hand.fingers.forEach((finger, j) => {
4336
4615
  finger.rotation.z = -hand.side * grip * (j % 2 ? 1.1 : 0.9);
@@ -4338,7 +4617,23 @@ var createPlayerAvatar = (THREE, options = {}) => {
4338
4617
  if (hand.th) hand.th.rotation.z = -hand.side * (0.32 + grip * 0.25);
4339
4618
  hand.hg.rotation.x = wristX;
4340
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;
4341
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
+ })();
4342
4637
  const legSide = (index) => index === 0 ? 1 : -1;
4343
4638
  let cycles = 0;
4344
4639
  let time = 0;
@@ -4397,10 +4692,29 @@ var createPlayerAvatar = (THREE, options = {}) => {
4397
4692
  ...strain ? { legL: -0.5, legR: 0.4, kneeL: 0.55, kneeR: 0.08, drop: -0.05 } : {}
4398
4693
  };
4399
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
+ };
4400
4714
  const holdingPose = (gait, itemUse) => {
4401
- const bob = Math.sin(cycles * TWO_PI) * 0.05 * gait;
4715
+ const bob2 = Math.sin(cycles * TWO_PI) * 0.05 * gait;
4402
4716
  if (!itemUse) {
4403
- return { armL: -0.45 + bob, armLz: 0.22, elbL: -1.05 };
4717
+ return { armL: -0.45 + bob2, armLz: 0.22, elbL: -1.05 };
4404
4718
  }
4405
4719
  const { id, position, rotation } = itemUse;
4406
4720
  if (id === "drink" || id === "sip" || id === "chug") {
@@ -4413,7 +4727,7 @@ var createPlayerAvatar = (THREE, options = {}) => {
4413
4727
  headX: -0.26 * tilt3
4414
4728
  };
4415
4729
  }
4416
- if (id === "open" || id === "close") {
4730
+ if (id === "open" || id === "close" || itemUse.gripL !== void 0) {
4417
4731
  const reach = clamp((0.22 - position[0]) / 0.12, 0, 1);
4418
4732
  return {
4419
4733
  armL: -0.45 - 0.35 * reach,
@@ -4512,6 +4826,31 @@ var createPlayerAvatar = (THREE, options = {}) => {
4512
4826
  drop: Math.abs(Math.sin(ph)) * WALK_BOB * gait
4513
4827
  };
4514
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
+ };
4515
4854
  const placeBody = (progress) => {
4516
4855
  const fk = progress * progress * (3 - 2 * progress);
4517
4856
  const flip = fract(cycles * 1.7) >= 0.5 ? 1 : 0;
@@ -4561,6 +4900,7 @@ var createPlayerAvatar = (THREE, options = {}) => {
4561
4900
  return run ? run.clip.id : null;
4562
4901
  },
4563
4902
  update(dt, state = {}) {
4903
+ armReach.restore();
4564
4904
  const speed = state.speed ?? 0;
4565
4905
  const moving = speed >= 0.05;
4566
4906
  const down = !!state.down;
@@ -4571,13 +4911,27 @@ var createPlayerAvatar = (THREE, options = {}) => {
4571
4911
  profile.bob = (running ? RUN_BOB : WALK_BOB) * gait;
4572
4912
  time = cycles / (walkCadence(Math.max(speed, 0)) * (running ? 1.3 : 1));
4573
4913
  animator.animate(body, time, { speed });
4914
+ bob = rig.fig.position.y - rig.dropBase;
4574
4915
  fall = fallSeconds <= 0 ? down ? 1 : 0 : clamp(fall + (down ? dt : -dt) / fallSeconds, 0, 1);
4575
4916
  resetLegYaw();
4576
- if (down) animator.setPose(rig, downPose());
4577
- else {
4578
- if (running) animator.setPose(rig, runningPose(gait, Boolean(state.armed || state.holding || state.itemUse)));
4579
- 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
+ }
4580
4933
  if (state.push) animator.setPose(rig, pushingPose(state.push));
4934
+ else if (state.door) animator.setPose(rig, doorPose(state.door));
4581
4935
  else if (state.armed) animator.setPose(rig, armed(state.pitch ?? 0, gait, state.hip ?? 0));
4582
4936
  else if (state.holding || state.itemUse) animator.setPose(rig, holdingPose(gait, state.itemUse));
4583
4937
  else if (!running) {
@@ -4589,20 +4943,33 @@ var createPlayerAvatar = (THREE, options = {}) => {
4589
4943
  });
4590
4944
  }
4591
4945
  }
4592
- placeBody(fall);
4593
- stepClip(dt, down, Boolean(state.crouch));
4594
- 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;
4595
4954
  const blow = run?.clip.events?.find((event) => (event.type === "impact" || event.type === "grip") && event.data?.kind)?.data?.kind;
4596
4955
  const ph = cycles * TWO_PI;
4597
4956
  hands.forEach((hand) => {
4598
4957
  const isRight = hand.side < 0;
4599
4958
  const holdRight = isRight && (!!state.holding || !!state.itemUse);
4600
- const holdLeft = !isRight && (state.itemUse?.id === "open" || state.itemUse?.id === "close");
4601
- 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;
4602
4961
  const wristSwing = holdRight || state.armed || running ? 0 : -Math.min(1, gait) * Math.sin(ph - 0.6) * 0.12 * hand.side;
4603
4962
  setHandGrip(hand, grip, wristSwing);
4604
4963
  });
4964
+ root.updateMatrixWorld(true);
4965
+ root.worldToLocal(eyeSocket.getWorldPosition(eyeLocal));
4966
+ eyeLocal.y -= (1 - eyeBob) * bob;
4967
+ },
4968
+ eye(out = new THREE.Vector3()) {
4969
+ root.updateWorldMatrix(true, false);
4970
+ return root.localToWorld(out.copy(eyeLocal));
4605
4971
  },
4972
+ setView: (mode, camera) => headHider.setView(mode, camera),
4606
4973
  play(clip, playOptions = {}) {
4607
4974
  const found = typeof clip === "string" ? clips.get(clip) : clip;
4608
4975
  if (!found) throw new Error(`Unknown pose clip: ${String(clip)}`);
@@ -4614,6 +4981,12 @@ var createPlayerAvatar = (THREE, options = {}) => {
4614
4981
  if (run) run.target = 0;
4615
4982
  },
4616
4983
  hand: (side) => sockets[side],
4984
+ shoulder: (side) => armOf[side].g,
4985
+ reach(targets, view) {
4986
+ if (run || fall > 0) return;
4987
+ root.updateMatrixWorld(true);
4988
+ armReach.apply(targets, view);
4989
+ },
4617
4990
  on(_type, listener) {
4618
4991
  listeners.add(listener);
4619
4992
  return () => void listeners.delete(listener);
@@ -4650,7 +5023,9 @@ var first_person_arms_default = {
4650
5023
  shoulder: [0.16, -0.36, 0.18],
4651
5024
  upper: 0.32,
4652
5025
  fore: 0.3,
4653
- reachShift: 0.35,
5026
+ reachShift: 0.42,
5027
+ reachBend: [1.35, 0.95],
5028
+ elbowView: [[0.96, -0.26, -0.06], null],
4654
5029
  wrist: 0.07,
4655
5030
  elbowPole: [0.45, -1, 0.2],
4656
5031
  shoulderLeft: [-0.07, -0.46, 0.18],
@@ -4677,22 +5052,14 @@ var first_person_arms_default = {
4677
5052
  };
4678
5053
 
4679
5054
  // src/first-person/hand.ts
4680
- var FINGERS = [
4681
- { z: -0.03, lengths: [0.034, 0.024, 0.02], width: 0.016 },
4682
- { z: -0.0105, lengths: [0.038, 0.027, 0.022], width: 0.0165 },
4683
- { z: 9e-3, lengths: [0.035, 0.025, 0.02], width: 0.0155 },
4684
- { z: 0.027, lengths: [0.029, 0.02, 0.018], width: 0.0135 }
4685
- ];
4686
- var PALM = { length: 0.09, thick: 0.026, width: 0.09, wristWidth: 0.066 };
4687
- var FINGER_THICK = 0.0135;
4688
- var CURL = { near: 1.05, mid: 1.25, far: 0.85, spread: 0.06 };
4689
- 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 };
4690
5058
  var createHandMesh = (THREE, skin, wristSkin = skin) => {
4691
5059
  const root = new THREE.Group();
4692
5060
  root.name = "first-person-hand";
4693
5061
  const geometries = [];
4694
- const box = (parent, sx, sy, sz, x, y, z, material = skin) => {
4695
- const geometry = new THREE.BoxGeometry(sx, sy, sz);
5062
+ const add = (parent, geometry, material, x = 0, y = 0, z = 0) => {
4696
5063
  geometries.push(geometry);
4697
5064
  const mesh = new THREE.Mesh(geometry, material);
4698
5065
  mesh.position.set(x, y, z);
@@ -4700,64 +5067,26 @@ var createHandMesh = (THREE, skin, wristSkin = skin) => {
4700
5067
  parent.add(mesh);
4701
5068
  return mesh;
4702
5069
  };
4703
- const palmGeometry = new THREE.BoxGeometry(PALM.length, PALM.thick, PALM.width);
4704
- const palmPosition = palmGeometry.getAttribute("position");
4705
- for (let i = 0; i < palmPosition.count; i++) {
4706
- if (palmPosition.getX(i) < 0) palmPosition.setZ(i, palmPosition.getZ(i) * PALM.wristWidth / PALM.width);
4707
- }
4708
- palmGeometry.computeVertexNormals();
4709
- geometries.push(palmGeometry);
4710
- const palm = new THREE.Mesh(palmGeometry, skin);
4711
- palm.frustumCulled = false;
4712
- root.add(palm);
4713
- 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);
4714
5077
  wristGeometry.rotateZ(Math.PI / 2);
4715
- geometries.push(wristGeometry);
4716
- const wrist = new THREE.Mesh(wristGeometry, wristSkin);
4717
- wrist.position.set(-PALM.length / 2 - 0.018, 0, 0);
4718
- wrist.frustumCulled = false;
4719
- root.add(wrist);
4720
- const joints = [];
4721
- FINGERS.forEach((finger, index) => {
4722
- const segments = [];
4723
- let parent = root;
4724
- let at = PALM.length / 2;
4725
- finger.lengths.forEach((length, k) => {
4726
- const group = new THREE.Group();
4727
- group.position.set(at, k === 0 ? 2e-3 : 0, k === 0 ? finger.z : 0);
4728
- const taper = 1 - k * 0.07;
4729
- box(group, length, FINGER_THICK * taper, finger.width * taper, length / 2, 0, 0);
4730
- parent.add(group);
4731
- segments.push(group);
4732
- parent = group;
4733
- at = length;
4734
- });
4735
- joints.push({ segments, weight: 1 + index * (CURL.spread / CURL.near) });
4736
- });
4737
- const thumbNear = new THREE.Group();
4738
- thumbNear.rotation.order = "YZX";
4739
- thumbNear.position.set(...THUMB.base);
4740
- box(thumbNear, THUMB.near, FINGER_THICK * 1.1, THUMB.width, THUMB.near / 2, 0, 0);
4741
- const thumbFar = new THREE.Group();
4742
- thumbFar.position.set(THUMB.near, 0, 0);
4743
- box(thumbFar, THUMB.far, FINGER_THICK, THUMB.width * 0.9, THUMB.far / 2, 0, 0);
4744
- thumbNear.add(thumbFar);
4745
- root.add(thumbNear);
5078
+ add(root, wristGeometry, wristSkin, -PALM.down - 0.03, 0, 0);
4746
5079
  let lastGrip = 0;
4747
5080
  let indexCurl;
4748
- const curl = [CURL.near, CURL.mid, CURL.far];
4749
- const applyIndex = () => {
4750
- const finger = joints[0];
4751
- if (!finger) return;
4752
- const k = indexCurl === void 0 ? lastGrip : Math.min(1, Math.max(0, indexCurl));
4753
- 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;
4754
5084
  };
5085
+ const applyIndex = () => curlFinger(0, indexCurl === void 0 ? lastGrip : Math.min(1, Math.max(0, indexCurl)));
4755
5086
  const setGrip = (grip) => {
4756
- const k = Math.min(1, Math.max(0, grip));
4757
- lastGrip = k;
4758
- for (const joint of joints) joint.segments.forEach((segment, i) => segment.rotation.z = -curl[i] * k * joint.weight);
4759
- thumbNear.rotation.set(0, 0.55 - 1.45 * k, -0.1 - 0.75 * k);
4760
- 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;
4761
5090
  applyIndex();
4762
5091
  };
4763
5092
  const setIndex = (value) => {
@@ -4775,33 +5104,12 @@ var createHandMesh = (THREE, skin, wristSkin = skin) => {
4775
5104
  };
4776
5105
  };
4777
5106
 
4778
- // src/first-person/ik.ts
4779
- var solveTwoBone = (root, target, upper, lower, pole, out) => {
4780
- const line = out.line.copy(target).sub(root);
4781
- let distance2 = line.length();
4782
- if (distance2 < 1e-6) line.set(0, 0, -1);
4783
- else line.divideScalar(distance2);
4784
- const min = Math.abs(upper - lower) + 1e-4;
4785
- const max = upper + lower - 1e-4;
4786
- out.clamped = distance2 > max || distance2 < min;
4787
- distance2 = Math.min(max, Math.max(min, distance2));
4788
- const along = (distance2 * distance2 + upper * upper - lower * lower) / (2 * distance2);
4789
- const across = Math.sqrt(Math.max(0, upper * upper - along * along));
4790
- const side = out.side.copy(pole);
4791
- side.addScaledVector(line, -side.dot(line));
4792
- if (side.lengthSq() < 1e-8) side.set(0, 1, 0).addScaledVector(line, -line.y);
4793
- if (side.lengthSq() < 1e-8) side.set(1, 0, 0);
4794
- side.normalize();
4795
- out.joint.copy(root).addScaledVector(line, along).addScaledVector(side, across);
4796
- out.end.copy(root).addScaledVector(line, distance2);
4797
- return out;
4798
- };
4799
- var createTwoBone = (THREE) => ({ joint: new THREE.Vector3(), end: new THREE.Vector3(), clamped: false, line: new THREE.Vector3(), side: new THREE.Vector3() });
4800
-
4801
5107
  // src/first-person/arms.ts
4802
5108
  var DEFAULT_BLEND = 0.18;
4803
5109
  var ELBOW_SEGMENTS = 8;
4804
5110
  var REACH_SLACK = 0.97;
5111
+ var ELBOW_VIEW_MARGIN = 0.86;
5112
+ var ELBOW_VIEW_PASSES = 4;
4805
5113
  var CONTACT_PROBE = 1.4;
4806
5114
  var CONTACT_MIN = 0.12;
4807
5115
  var CONTACT_MARGIN = 5e-3;
@@ -4950,6 +5258,11 @@ function createFirstPersonArms(THREE, options = {}) {
4950
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]);
4951
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]);
4952
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
+ };
4953
5266
  const hip = new THREE.Vector3(...rig.hip);
4954
5267
  const euler = new THREE.Euler(0, 0, 0, "YZX");
4955
5268
  const quaternionOf = (r) => new THREE.Quaternion().setFromEuler(euler.set(r[0], r[1], r[2], "YZX"));
@@ -5039,15 +5352,45 @@ function createFirstPersonArms(THREE, options = {}) {
5039
5352
  out.scale = base.scale;
5040
5353
  return out;
5041
5354
  };
5042
- 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) => {
5043
5378
  copyPose(arm.drawn, pose);
5044
5379
  const scale = pose.scale;
5045
5380
  fingers.set(1, 0, 0).applyQuaternion(pose.q);
5046
5381
  wristAt.copy(pose.p).addScaledVector(fingers, -rig.wrist * scale);
5047
5382
  shoulderOf(arm.sign, shoulder).add(bobOffset);
5048
- 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);
5049
5389
  if (excess > 0 && rig.reachShift > 0) shoulder.addScaledVector(towards.subVectors(wristAt, shoulder).normalize(), Math.min(excess, rig.reachShift));
5050
5390
  solveTwoBone(shoulder, wristAt, rig.upper, rig.fore, poleOf(arm.sign), bone);
5391
+ finishArm(arm, pose, scale);
5392
+ };
5393
+ const finishArm = (arm, pose, scale) => {
5051
5394
  palmAt.copy(bone.end).addScaledVector(fingers, rig.wrist * scale);
5052
5395
  arm.drawn.p.copy(palmAt);
5053
5396
  arm.anchor.position.copy(palmAt);
@@ -5159,7 +5502,7 @@ function createFirstPersonArms(THREE, options = {}) {
5159
5502
  dropped.p.y -= AWAY_DROP * ease(arm.away);
5160
5503
  pose = dropped;
5161
5504
  }
5162
- drawArm(arm, pose);
5505
+ drawArm(arm, pose, mode === "target" && targets?.elbows !== "body" ? viewOf(state.camera) : null);
5163
5506
  const shown = arm.away < AWAY_HIDDEN;
5164
5507
  for (const part of [arm.anchor, arm.upper, arm.fore, arm.cuff, arm.elbow]) part.visible = shown;
5165
5508
  }
@@ -5207,7 +5550,7 @@ var impactOf = (clip) => clip.events?.find((event) => event.type === "impact")?.
5207
5550
  var isPunch = (clip) => clip.events?.some((event) => event.type === "impact" && event.data?.kind === "punch") ?? false;
5208
5551
  var GUARD_SPEED = 0.3;
5209
5552
  var toRoomAvatar = (avatar, options = {}) => {
5210
- const arms = options.firstPerson === false ? null : createFirstPersonArms(avatar.three, {
5553
+ const arms = options.firstPerson === false || options.firstPerson === void 0 ? null : createFirstPersonArms(avatar.three, {
5211
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 },
5212
5555
  clips: options.firstPersonClips ?? FIRST_PERSON_CLIPS,
5213
5556
  hideWhenIdle: true,
@@ -5228,7 +5571,7 @@ var toRoomAvatar = (avatar, options = {}) => {
5228
5571
  let guarding = false;
5229
5572
  const stepGuard = (dt, state) => {
5230
5573
  if (!guard) return;
5231
- 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;
5232
5575
  if (strikeLeft > 0) {
5233
5576
  strikeLeft -= dt;
5234
5577
  if (strikeLeft <= 0 && guardLeft > 0 && free) {
@@ -5247,7 +5590,8 @@ var toRoomAvatar = (avatar, options = {}) => {
5247
5590
  root: avatar.root,
5248
5591
  avatar,
5249
5592
  arms,
5250
- ...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),
5251
5595
  update: (dt, state) => {
5252
5596
  stepGuard(dt, state);
5253
5597
  avatar.update(dt, {
@@ -5259,10 +5603,14 @@ var toRoomAvatar = (avatar, options = {}) => {
5259
5603
  pitch: -state.pitch,
5260
5604
  ...state.holding !== void 0 ? { holding: state.holding } : {},
5261
5605
  ...state.itemUse !== void 0 ? { itemUse: state.itemUse } : {},
5262
- ...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 } : {}
5263
5609
  });
5264
5610
  },
5265
5611
  hand: (side) => avatar.hand(side),
5612
+ shoulder: (side) => avatar.shoulder(side),
5613
+ reach: (targets, view) => avatar.reach(targets, view),
5266
5614
  strike: (clipId, strikeOptions) => {
5267
5615
  const clip = strikes.get(clipId);
5268
5616
  if (!clip) return null;