@adatechnology/game-characters-three 0.6.0 → 0.7.1

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);
@@ -2674,6 +2675,7 @@ var ringWound = (three, materials, parent, radius, y = 0) => {
2674
2675
  };
2675
2676
 
2676
2677
  // src/wounds/flying.ts
2678
+ import { discardObject3D } from "@adatechnology/game-shapes-three";
2677
2679
  var GRAVITY2 = 9.8;
2678
2680
  var FLOOR_OFFSET = 0.08;
2679
2681
  var BOUNCE_LIMIT = 1.2;
@@ -2686,7 +2688,10 @@ var createFlyingPieces = (three, { random, groundAt = () => 0, onRest, max = 40
2686
2688
  const quaternion = new three.Quaternion();
2687
2689
  const euler = new three.Euler();
2688
2690
  const trim = () => {
2689
- while (pieces.length > max) pieces.shift()?.object.removeFromParent();
2691
+ while (pieces.length > max) {
2692
+ const piece = pieces.shift();
2693
+ if (piece) discardObject3D(piece.object);
2694
+ }
2690
2695
  };
2691
2696
  const add = (object, velocity, spin, tag) => {
2692
2697
  const piece = { object, velocity, spin, rest: false, ...tag ? { tag } : {} };
@@ -2735,7 +2740,7 @@ var createFlyingPieces = (three, { random, groundAt = () => 0, onRest, max = 40
2735
2740
  }
2736
2741
  },
2737
2742
  clear() {
2738
- pieces.splice(0).forEach((piece) => piece.object.removeFromParent());
2743
+ discardObject3D(pieces.splice(0).map((piece) => piece.object));
2739
2744
  }
2740
2745
  };
2741
2746
  };
@@ -3751,7 +3756,8 @@ function createCharactersTestModule(options = {}) {
3751
3756
  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
3757
  let kit = makeKit();
3753
3758
  let undead = options.createUndead ? options.createUndead(kit) : null;
3754
- const random = createRng3(options.seed ?? 1);
3759
+ let stream = createRng3(options.seed ?? 1);
3760
+ const random = () => stream();
3755
3761
  let seedCounter = 100;
3756
3762
  let host = null;
3757
3763
  let three = null;
@@ -4131,6 +4137,14 @@ function createCharactersTestModule(options = {}) {
4131
4137
  wounds = createWounds(lent.THREE, { random, scene: scene2, groundAt: (x, z) => lent.groundAt(x, z), ...bridge ? { bridge } : {}, ...sound ? { sound } : {} });
4132
4138
  for (const entry of entries.values()) trackEntry(entry);
4133
4139
  const stopChange = lent.bench?.onChange(onChange);
4140
+ const stopReset = lent.onReset?.(() => {
4141
+ stream = createRng3(options.seed ?? 1);
4142
+ seedCounter = 100;
4143
+ time = 0;
4144
+ selected = null;
4145
+ lastCrossed = 0;
4146
+ lastEnergy = 1;
4147
+ });
4134
4148
  const stopEvents = lent.on((event) => {
4135
4149
  if (event.type !== "explosion") return;
4136
4150
  for (const entry of entries.values()) {
@@ -4144,6 +4158,8 @@ function createCharactersTestModule(options = {}) {
4144
4158
  time += dt;
4145
4159
  for (const [id, entry] of entries) {
4146
4160
  if (!entry.root.parent) {
4161
+ if (entry.body) wounds?.untrack(entry.body);
4162
+ if (selected === entry) selected = null;
4147
4163
  entries.delete(id);
4148
4164
  continue;
4149
4165
  }
@@ -4155,6 +4171,7 @@ function createCharactersTestModule(options = {}) {
4155
4171
  });
4156
4172
  return () => {
4157
4173
  stopChange?.();
4174
+ stopReset?.();
4158
4175
  stopEvents();
4159
4176
  stopFrame();
4160
4177
  clearOverlays();
@@ -4201,6 +4218,7 @@ import {
4201
4218
  filterPoseChannels,
4202
4219
  samplePoseClip,
4203
4220
  walkCadence,
4221
+ stepCascade as stepCascade2,
4204
4222
  walkGait
4205
4223
  } from "@adatechnology/game-characters";
4206
4224
 
@@ -4276,8 +4294,22 @@ var ARM_REACH = {
4276
4294
  slack: 0.96,
4277
4295
  /** Where the elbow points: down and out from the body, a little back (body space, x outward). */
4278
4296
  elbowPole: [0.55, -1, -0.25],
4297
+ /** 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. */
4298
+ elbowPoleSupport: [0.1, -1, -0.2],
4279
4299
  /** From the wrist joint to the middle of the palm, metres of the body. */
4280
- palm: 0.042
4300
+ palm: 0.042,
4301
+ /** 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). */
4302
+ viewBend: [1.35, 0.95],
4303
+ /** In a view a hand stays this far before a surface, metres. */
4304
+ contactMargin: 5e-3,
4305
+ /** In a view a hand is never nearer than this to the eye, metres. */
4306
+ contactNearest: 0.12,
4307
+ /** How far in front of the eye a surface is looked for, metres. */
4308
+ contactProbe: 1.4,
4309
+ /** How far a hand reaches forward of its middle at least (the thickness of the palm), and per metre of fingers pointing ahead. */
4310
+ contactPalm: 0.03,
4311
+ /** From the middle of the palm to the fingertips. */
4312
+ contactFingers: 0.085
4281
4313
  };
4282
4314
  var createArmReach = (THREE, rig, hands) => {
4283
4315
  const arms = {
@@ -4298,11 +4330,17 @@ var createArmReach = (THREE, rig, hands) => {
4298
4330
  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
4331
  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
4332
  const basis = new THREE.Matrix4();
4333
+ 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() };
4334
+ let bendLimit = null;
4301
4335
  const yAxis = new THREE.Vector3(0, 1, 0);
4302
4336
  const lengthsOf = (arm) => {
4303
4337
  const scale = arm.g.getWorldScale(v.a).y;
4304
4338
  return { upper: arm.el.position.length() * scale, fore: arm.hand.position.length() * scale, palm: ARM_REACH.palm * scale };
4305
4339
  };
4340
+ const limitOf = (side, upperLength, fore) => {
4341
+ if (!bendLimit) return (upperLength + fore) * ARM_REACH.slack;
4342
+ return Math.sqrt(upperLength * upperLength + fore * fore + 2 * upperLength * fore * Math.cos(bendLimit[side === "R" ? 0 : 1]));
4343
+ };
4306
4344
  const wristOf = (side, pose, palm) => {
4307
4345
  q.hand.set(...pose.quaternion).multiply(handBasis[side]);
4308
4346
  v.palm.set(...pose.position);
@@ -4311,14 +4349,14 @@ var createArmReach = (THREE, rig, hands) => {
4311
4349
  const turnChest = (pose) => {
4312
4350
  const { upper: up, fore, palm } = lengthsOf(arms.L);
4313
4351
  wristOf("L", pose, palm);
4314
- const excess = arms.L.g.getWorldPosition(v.shoulder).distanceTo(v.wrist) - (up + fore) * ARM_REACH.slack;
4352
+ const excess = arms.L.g.getWorldPosition(v.shoulder).distanceTo(v.wrist) - limitOf("L", up, fore);
4315
4353
  if (excess <= 0) return;
4316
4354
  const chestRadius = Math.hypot(arms.L.g.position.x, arms.L.g.position.z) * arms.L.g.getWorldScale(v.a).y;
4317
4355
  upper.rotation.y -= Math.min(ARM_REACH.chestTurn, excess / Math.max(0.05, chestRadius));
4318
4356
  upper.updateMatrixWorld(true);
4319
4357
  };
4320
- const protract = (arm, length) => {
4321
- const excess = arm.g.getWorldPosition(v.shoulder).distanceTo(v.wrist) - length * ARM_REACH.slack;
4358
+ const protract = (arm, limit) => {
4359
+ const excess = arm.g.getWorldPosition(v.shoulder).distanceTo(v.wrist) - limit;
4322
4360
  if (excess <= 0) return;
4323
4361
  const parent = arm.g.parent;
4324
4362
  v.b.copy(v.wrist).sub(v.shoulder).normalize().multiplyScalar(Math.min(excess, ARM_REACH.protraction)).add(v.shoulder);
@@ -4339,11 +4377,13 @@ var createArmReach = (THREE, rig, hands) => {
4339
4377
  const arm = arms[side];
4340
4378
  const { upper: up, fore, palm } = lengthsOf(arm);
4341
4379
  wristOf(side, pose, palm);
4342
- protract(arm, up + fore);
4380
+ protract(arm, limitOf(side, up, fore));
4343
4381
  arm.g.getWorldPosition(v.shoulder);
4344
4382
  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));
4383
+ const pole = side === "L" ? ARM_REACH.elbowPoleSupport : ARM_REACH.elbowPole;
4384
+ v.pole.set(pole[0] * arm.outward, pole[1], pole[2]).applyQuaternion(q.body);
4385
+ const lockLimit = Math.sqrt(up * up + fore * fore + 2 * up * fore * Math.cos(ARM_REACH.elbowMin));
4386
+ const most = bendLimit ? Math.min(lockLimit, limitOf(side, up, fore)) : lockLimit;
4347
4387
  const least = Math.sqrt(up * up + fore * fore + 2 * up * fore * Math.cos(ARM_REACH.elbowMax));
4348
4388
  const line = v.c.copy(v.wrist).sub(v.shoulder);
4349
4389
  const distance2 = Math.min(most, Math.max(least, line.length()));
@@ -4368,6 +4408,20 @@ var createArmReach = (THREE, rig, hands) => {
4368
4408
  arm.el.getWorldQuaternion(q.parent);
4369
4409
  arm.hand.quaternion.copy(limitWrist(q.local.copy(q.parent.invert()).multiply(q.hand)));
4370
4410
  };
4411
+ const keepOutside = (pose, outside) => {
4412
+ const place = v.a.set(...pose.position).sub(camera.position).applyQuaternion(camera.inverse);
4413
+ camera.probe.set(place.x, place.y, 0).applyQuaternion(camera.quaternion).add(camera.position);
4414
+ camera.forward.set(0, 0, -1).applyQuaternion(camera.quaternion);
4415
+ 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);
4416
+ if (distance2 === null) return pose;
4417
+ camera.hand.set(...pose.quaternion).premultiply(camera.inverse);
4418
+ const ahead = Math.max(ARM_REACH.contactPalm, -camera.fingers.set(1, 0, 0).applyQuaternion(camera.hand).z * ARM_REACH.contactFingers);
4419
+ const limit = Math.max(outside.nearest ?? ARM_REACH.contactNearest, distance2 - ahead - (outside.margin ?? ARM_REACH.contactMargin));
4420
+ if (-place.z <= limit) return pose;
4421
+ place.z = -limit;
4422
+ place.applyQuaternion(camera.quaternion).add(camera.position);
4423
+ return { ...pose, position: [place.x, place.y, place.z] };
4424
+ };
4371
4425
  const save = () => {
4372
4426
  saved.twist = upper.rotation.y;
4373
4427
  for (const side of ["R", "L"]) {
@@ -4380,14 +4434,24 @@ var createArmReach = (THREE, rig, hands) => {
4380
4434
  }
4381
4435
  };
4382
4436
  return {
4383
- apply(targets) {
4437
+ apply(targets, view) {
4384
4438
  if (!targets || !targets.R && !targets.L) return;
4385
4439
  save();
4386
4440
  applied = true;
4387
4441
  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);
4442
+ let { R, L } = targets;
4443
+ bendLimit = view ? view.reachBend ?? ARM_REACH.viewBend : null;
4444
+ if (view?.keepOutside) {
4445
+ view.camera.updateWorldMatrix(true, false);
4446
+ view.camera.matrixWorld.decompose(camera.position, camera.quaternion, v.a);
4447
+ camera.inverse.copy(camera.quaternion).invert();
4448
+ if (R) R = keepOutside(R, view.keepOutside);
4449
+ if (L) L = keepOutside(L, view.keepOutside);
4450
+ }
4451
+ if (L && R) turnChest(L);
4452
+ if (R) reach("R", R);
4453
+ if (L) reach("L", L);
4454
+ bendLimit = null;
4391
4455
  },
4392
4456
  restore() {
4393
4457
  if (!applied) return;
@@ -4405,12 +4469,56 @@ var createArmReach = (THREE, rig, hands) => {
4405
4469
  };
4406
4470
  };
4407
4471
 
4472
+ // src/player/head-hiding.ts
4473
+ var isDrawable = (object) => {
4474
+ const flagged = object;
4475
+ return Boolean(flagged.isMesh || flagged.isSprite || flagged.isPoints);
4476
+ };
4477
+ var createHeadHider = (roots) => {
4478
+ let isHiding = false;
4479
+ let viewCamera = null;
4480
+ const drawables = [];
4481
+ for (const root of roots) root.traverse((object) => isDrawable(object) && drawables.push(object));
4482
+ for (const drawable of drawables) {
4483
+ const previousBefore = drawable.onBeforeRender;
4484
+ const previousAfter = drawable.onAfterRender;
4485
+ let saved = [];
4486
+ drawable.onBeforeRender = (renderer, scene2, camera, geometry, material, group) => {
4487
+ previousBefore.call(drawable, renderer, scene2, camera, geometry, material, group);
4488
+ if (!isHiding || camera !== viewCamera) return;
4489
+ const drawn = Array.isArray(drawable.material) ? drawable.material : [material];
4490
+ saved = drawn.map((entry) => ({ material: entry, colorWrite: entry.colorWrite, depthWrite: entry.depthWrite }));
4491
+ for (const entry of drawn) {
4492
+ entry.colorWrite = false;
4493
+ entry.depthWrite = false;
4494
+ }
4495
+ };
4496
+ drawable.onAfterRender = (renderer, scene2, camera, geometry, material, group) => {
4497
+ previousAfter.call(drawable, renderer, scene2, camera, geometry, material, group);
4498
+ for (const flags of saved) {
4499
+ flags.material.colorWrite = flags.colorWrite;
4500
+ flags.material.depthWrite = flags.depthWrite;
4501
+ }
4502
+ saved = [];
4503
+ };
4504
+ }
4505
+ return {
4506
+ setView(mode, camera) {
4507
+ isHiding = mode === "first" && camera !== null;
4508
+ viewCamera = camera;
4509
+ }
4510
+ };
4511
+ };
4512
+
4408
4513
  // src/player/avatar.ts
4409
4514
  var TWO_PI = Math.PI * 2;
4410
4515
  var MAX_GAIT = 1.4;
4411
4516
  var CLIP_BLEND = 0.12;
4412
4517
  var CLIP_CASCADE = 5;
4413
4518
  var FALL_SECONDS2 = 0.3;
4519
+ var BUILDER_EYE = [0, 1.815, 0.16];
4520
+ var EYE_OFFSET = [0, -0.068, 0];
4521
+ var EYE_BOB = 0.5;
4414
4522
  var HIP_HEIGHT = 0.95;
4415
4523
  var WALK_ARM = 0.28;
4416
4524
  var WALK_BOB = 0.035;
@@ -4420,7 +4528,11 @@ var RUN_BOB = 0.05;
4420
4528
  var REST_ARM_Z = 0.18;
4421
4529
  var CROUCH = { thigh: -0.95, thighWalk: 0.3, shin: 1.9, lean: 0.25, drop: -0.35 };
4422
4530
  var AIMED = { shoulder: -1.42, perAim: 0.85, swing: 0.02, zRight: -0.54, zLeft: 0.36, elbowRight: -0.04, elbowLeft: -0.12 };
4531
+ var SIT = { thigh: -1.5, lean: -0.08, arm: -0.45, armZ: 0.12, elbow: -0.75 };
4532
+ var LIE = { knee: 0.08, armZ: 0.12, elbow: -0.1 };
4423
4533
  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 };
4534
+ var DOOR_SHOULDER = 1.42;
4535
+ var DOOR_ARM = 0.62;
4424
4536
  var HAND_OFFSET = { x: 0, y: -0.33, z: 0.04 };
4425
4537
  var hexOf = (value) => typeof value === "string" ? Number.parseInt(value, 16) : value;
4426
4538
  var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
@@ -4450,6 +4562,8 @@ var createPlayerAvatar = (THREE, options = {}) => {
4450
4562
  const fallSeconds = options.fallSeconds ?? FALL_SECONDS2;
4451
4563
  const smooth = options.cascade ?? true;
4452
4564
  const clipCascade = options.clipCascade ?? CLIP_CASCADE;
4565
+ const eyeBob = options.eyeBob ?? EYE_BOB;
4566
+ const eyeOffset = options.eyeOffset ?? EYE_OFFSET;
4453
4567
  const root = new THREE.Group();
4454
4568
  const tilt = new THREE.Group();
4455
4569
  tilt.add(body);
@@ -4509,7 +4623,23 @@ var createPlayerAvatar = (THREE, options = {}) => {
4509
4623
  if (hand.th) hand.th.rotation.z = -hand.side * (0.32 + grip * 0.25);
4510
4624
  hand.hg.rotation.x = wristX;
4511
4625
  };
4626
+ const eyeSocket = new THREE.Object3D();
4627
+ eyeSocket.name = "eye";
4628
+ eyeSocket.position.set(BUILDER_EYE[0] + eyeOffset[0], BUILDER_EYE[1] + eyeOffset[1], BUILDER_EYE[2] + eyeOffset[2]);
4629
+ rig.headG.children[0].add(eyeSocket);
4630
+ const headHider = createHeadHider([rig.headG, ...rig.neck ? [rig.neck] : []]);
4631
+ const eyeLocal = new THREE.Vector3();
4632
+ let bob = 0;
4633
+ let hipWithoutBob = null;
4512
4634
  const legRestX = rig.legs.map((leg) => leg.g.position.x);
4635
+ const legLengths = (() => {
4636
+ const thigh = rig.legs[0].g;
4637
+ const shin = rig.legs[0].kn;
4638
+ root.updateMatrixWorld(true);
4639
+ const hipY = thigh.getWorldPosition(new THREE.Vector3()).y;
4640
+ const kneeY = shin.getWorldPosition(new THREE.Vector3()).y;
4641
+ return { hipBelowTrunk: HIP_HEIGHT - hipY, thigh: hipY - kneeY, shin: kneeY - new THREE.Box3().setFromObject(shin).min.y };
4642
+ })();
4513
4643
  const legSide = (index) => index === 0 ? 1 : -1;
4514
4644
  let cycles = 0;
4515
4645
  let time = 0;
@@ -4568,10 +4698,29 @@ var createPlayerAvatar = (THREE, options = {}) => {
4568
4698
  ...strain ? { legL: -0.5, legR: 0.4, kneeL: 0.55, kneeR: 0.08, drop: -0.05 } : {}
4569
4699
  };
4570
4700
  };
4701
+ const doorPose = (door) => {
4702
+ const { phase, progress } = door;
4703
+ const on = phase === "reach" ? progress : phase === "release" ? 1 - progress : 1;
4704
+ const swing = phase === "swing" ? progress : phase === "release" ? 1 : 0;
4705
+ const shake = phase === "rattle" ? Math.sin(time * 55) * 0.06 * (1 - progress) : 0;
4706
+ const drop = clamp((DOOR_SHOULDER - door.height) / DOOR_ARM, -0.9, 1);
4707
+ const low = clamp((0.75 - door.height) / 0.5, 0, 1);
4708
+ const raise = -Math.acos(clamp(drop, -1, 1)) * on - 0.35 * low * on;
4709
+ const elbow = door.pull ? -0.35 - 0.75 * swing : -0.95 + 0.75 * swing;
4710
+ return {
4711
+ armR: raise + shake,
4712
+ armRz: REST_ARM_Z * (1 - on) + 0.05 * on,
4713
+ elbR: -0.12 + (elbow + 0.12) * on,
4714
+ armLz: -REST_ARM_Z,
4715
+ elbL: -0.12,
4716
+ lean: ((door.pull ? -0.06 : 0.1) * swing + 0.35 * low) * on,
4717
+ headX: 0.06 * on + 0.2 * low * on
4718
+ };
4719
+ };
4571
4720
  const holdingPose = (gait, itemUse) => {
4572
- const bob = Math.sin(cycles * TWO_PI) * 0.05 * gait;
4721
+ const bob2 = Math.sin(cycles * TWO_PI) * 0.05 * gait;
4573
4722
  if (!itemUse) {
4574
- return { armL: -0.45 + bob, armLz: 0.22, elbL: -1.05 };
4723
+ return { armL: -0.45 + bob2, armLz: 0.22, elbL: -1.05 };
4575
4724
  }
4576
4725
  const { id, position, rotation } = itemUse;
4577
4726
  if (id === "drink" || id === "sip" || id === "chug") {
@@ -4584,7 +4733,7 @@ var createPlayerAvatar = (THREE, options = {}) => {
4584
4733
  headX: -0.26 * tilt3
4585
4734
  };
4586
4735
  }
4587
- if (id === "open" || id === "close") {
4736
+ if (id === "open" || id === "close" || itemUse.gripL !== void 0) {
4588
4737
  const reach = clamp((0.22 - position[0]) / 0.12, 0, 1);
4589
4738
  return {
4590
4739
  armL: -0.45 - 0.35 * reach,
@@ -4683,6 +4832,31 @@ var createPlayerAvatar = (THREE, options = {}) => {
4683
4832
  drop: Math.abs(Math.sin(ph)) * WALK_BOB * gait
4684
4833
  };
4685
4834
  };
4835
+ const sitPose = (hip) => {
4836
+ const knee = hip - legLengths.hipBelowTrunk - legLengths.thigh * Math.cos(SIT.thigh);
4837
+ const shinFromVertical = Math.acos(clamp(knee / legLengths.shin, 0, 1));
4838
+ const bend = -SIT.thigh - shinFromVertical;
4839
+ return {
4840
+ legL: SIT.thigh,
4841
+ legR: SIT.thigh,
4842
+ kneeL: bend,
4843
+ kneeR: bend,
4844
+ lean: SIT.lean,
4845
+ drop: hip - HIP_HEIGHT,
4846
+ armR: SIT.arm,
4847
+ armL: SIT.arm,
4848
+ armRz: SIT.armZ,
4849
+ armLz: -SIT.armZ,
4850
+ elbR: SIT.elbow,
4851
+ elbL: SIT.elbow
4852
+ };
4853
+ };
4854
+ 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 });
4855
+ const layBody = (hip) => {
4856
+ tilt.rotation.set(-Math.PI / 2, 0, 0, "XYZ");
4857
+ const pivot = new THREE.Vector3(0, HIP_HEIGHT, 0).applyEuler(tilt.rotation);
4858
+ tilt.position.set(0, hip, 0).sub(pivot);
4859
+ };
4686
4860
  const placeBody = (progress) => {
4687
4861
  const fk = progress * progress * (3 - 2 * progress);
4688
4862
  const flip = fract(cycles * 1.7) >= 0.5 ? 1 : 0;
@@ -4743,13 +4917,27 @@ var createPlayerAvatar = (THREE, options = {}) => {
4743
4917
  profile.bob = (running ? RUN_BOB : WALK_BOB) * gait;
4744
4918
  time = cycles / (walkCadence(Math.max(speed, 0)) * (running ? 1.3 : 1));
4745
4919
  animator.animate(body, time, { speed });
4920
+ bob = rig.fig.position.y - rig.dropBase;
4746
4921
  fall = fallSeconds <= 0 ? down ? 1 : 0 : clamp(fall + (down ? dt : -dt) / fallSeconds, 0, 1);
4747
4922
  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));
4923
+ const seat = down ? null : state.seat ?? null;
4924
+ if (down) {
4925
+ animator.setPose(rig, downPose());
4926
+ bob = rig.fig.position.y - rig.dropBase;
4927
+ } else if (seat) {
4928
+ animator.setPose(rig, seat.pose === "lie" ? liePose() : sitPose(seat.hip));
4929
+ bob = 0;
4930
+ } else {
4931
+ if (running) {
4932
+ animator.setPose(rig, runningPose(gait, Boolean(state.armed || state.holding || state.itemUse)));
4933
+ bob = rig.fig.position.y - rig.dropBase;
4934
+ }
4935
+ if (state.crouch) {
4936
+ animator.setPose(rig, crouchPose(gait));
4937
+ bob = rig.fig.position.y - rig.dropBase - CROUCH.drop;
4938
+ }
4752
4939
  if (state.push) animator.setPose(rig, pushingPose(state.push));
4940
+ else if (state.door) animator.setPose(rig, doorPose(state.door));
4753
4941
  else if (state.armed) animator.setPose(rig, armed(state.pitch ?? 0, gait, state.hip ?? 0));
4754
4942
  else if (state.holding || state.itemUse) animator.setPose(rig, holdingPose(gait, state.itemUse));
4755
4943
  else if (!running) {
@@ -4761,20 +4949,33 @@ var createPlayerAvatar = (THREE, options = {}) => {
4761
4949
  });
4762
4950
  }
4763
4951
  }
4764
- placeBody(fall);
4765
- stepClip(dt, down, Boolean(state.crouch));
4766
- if (smooth) animator.cascade(rig, run && !run.clip.loop ? dt * clipCascade : dt);
4952
+ if (seat?.pose === "lie") layBody(seat.hip);
4953
+ else placeBody(fall);
4954
+ stepClip(dt, down, Boolean(state.crouch || seat));
4955
+ const cascadeDt = run && !run.clip.loop ? dt * clipCascade : dt;
4956
+ const targetWithoutBob = rig.fig.position.y - bob;
4957
+ if (smooth) animator.cascade(rig, cascadeDt);
4958
+ hipWithoutBob = smooth && hipWithoutBob !== null ? stepCascade2(hipWithoutBob, targetWithoutBob, cascadeDt, "hip") : targetWithoutBob;
4959
+ bob = rig.fig.position.y - hipWithoutBob;
4767
4960
  const blow = run?.clip.events?.find((event) => (event.type === "impact" || event.type === "grip") && event.data?.kind)?.data?.kind;
4768
4961
  const ph = cycles * TWO_PI;
4769
4962
  hands.forEach((hand) => {
4770
4963
  const isRight = hand.side < 0;
4771
4964
  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;
4965
+ const holdLeft = !isRight && (state.itemUse?.id === "open" || state.itemUse?.id === "close" || state.itemUse?.gripL !== void 0);
4966
+ 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
4967
  const wristSwing = holdRight || state.armed || running ? 0 : -Math.min(1, gait) * Math.sin(ph - 0.6) * 0.12 * hand.side;
4775
4968
  setHandGrip(hand, grip, wristSwing);
4776
4969
  });
4970
+ root.updateMatrixWorld(true);
4971
+ root.worldToLocal(eyeSocket.getWorldPosition(eyeLocal));
4972
+ eyeLocal.y -= (1 - eyeBob) * bob;
4777
4973
  },
4974
+ eye(out = new THREE.Vector3()) {
4975
+ root.updateWorldMatrix(true, false);
4976
+ return root.localToWorld(out.copy(eyeLocal));
4977
+ },
4978
+ setView: (mode, camera) => headHider.setView(mode, camera),
4778
4979
  play(clip, playOptions = {}) {
4779
4980
  const found = typeof clip === "string" ? clips.get(clip) : clip;
4780
4981
  if (!found) throw new Error(`Unknown pose clip: ${String(clip)}`);
@@ -4786,10 +4987,11 @@ var createPlayerAvatar = (THREE, options = {}) => {
4786
4987
  if (run) run.target = 0;
4787
4988
  },
4788
4989
  hand: (side) => sockets[side],
4789
- reach(targets) {
4990
+ shoulder: (side) => armOf[side].g,
4991
+ reach(targets, view) {
4790
4992
  if (run || fall > 0) return;
4791
4993
  root.updateMatrixWorld(true);
4792
- armReach.apply(targets);
4994
+ armReach.apply(targets, view);
4793
4995
  },
4794
4996
  on(_type, listener) {
4795
4997
  listeners.add(listener);
@@ -4811,8 +5013,236 @@ var createPlayerAvatar = (THREE, options = {}) => {
4811
5013
  };
4812
5014
 
4813
5015
  // src/player/room-avatar.ts
4814
- import { ATTACK_CLIPS, FIRST_PERSON_CLIPS } from "@adatechnology/game-characters-people";
5016
+ import { ATTACK_CLIPS } from "@adatechnology/game-characters-people";
4815
5017
  import { mirrorPoseClip } from "@adatechnology/game-characters";
5018
+ var impactOf = (clip) => clip.events?.find((event) => event.type === "impact")?.t ?? null;
5019
+ var isPunch = (clip) => clip.events?.some((event) => event.type === "impact" && event.data?.kind === "punch") ?? false;
5020
+ var GUARD_SPEED = 0.3;
5021
+ var toRoomAvatar = (avatar, options = {}) => {
5022
+ const strikes = new Map((options.strikeClips ?? ATTACK_CLIPS).map((clip) => [clip.id, clip]));
5023
+ const mirrored = /* @__PURE__ */ new Map();
5024
+ const leftOf = (clip) => {
5025
+ let found = mirrored.get(clip.id);
5026
+ if (!found) mirrored.set(clip.id, found = mirrorPoseClip(clip, `${clip.id}_left`));
5027
+ return found;
5028
+ };
5029
+ const guard = options.guardClip === false ? void 0 : strikes.get(options.guardClip ?? "fight_guard");
5030
+ const guardHold = options.guardHold ?? 1.2;
5031
+ const punchBlend = options.punchBlend ?? 0.05;
5032
+ let strikeLeft = 0;
5033
+ let guardLeft = 0;
5034
+ let guarding = false;
5035
+ const stepGuard = (dt, state) => {
5036
+ if (!guard) return;
5037
+ const free = state.speed < GUARD_SPEED && !state.down && !state.armed && !state.holding && !state.push && !state.door && !state.itemUse && !state.running;
5038
+ if (strikeLeft > 0) {
5039
+ strikeLeft -= dt;
5040
+ if (strikeLeft <= 0 && guardLeft > 0 && free) {
5041
+ avatar.play(guard);
5042
+ guarding = true;
5043
+ }
5044
+ return;
5045
+ }
5046
+ guardLeft = Math.max(0, guardLeft - dt);
5047
+ if (guarding && (guardLeft <= 0 || !free || avatar.playing !== guard.id)) {
5048
+ if (avatar.playing === guard.id) avatar.stop();
5049
+ guarding = false;
5050
+ }
5051
+ };
5052
+ return {
5053
+ root: avatar.root,
5054
+ avatar,
5055
+ eye: (out) => avatar.eye(out),
5056
+ setView: (mode, camera) => avatar.setView(mode, camera),
5057
+ update: (dt, state) => {
5058
+ stepGuard(dt, state);
5059
+ avatar.update(dt, {
5060
+ speed: state.speed,
5061
+ running: state.running,
5062
+ crouch: state.crouch,
5063
+ down: state.down,
5064
+ armed: state.armed,
5065
+ pitch: -state.pitch,
5066
+ ...state.holding !== void 0 ? { holding: state.holding } : {},
5067
+ ...state.itemUse !== void 0 ? { itemUse: state.itemUse } : {},
5068
+ ...state.push !== void 0 ? { push: state.push } : {},
5069
+ ...state.seat !== void 0 ? { seat: state.seat } : {},
5070
+ ...state.door !== void 0 ? { door: state.door } : {}
5071
+ });
5072
+ },
5073
+ hand: (side) => avatar.hand(side),
5074
+ shoulder: (side) => avatar.shoulder(side),
5075
+ reach: (targets, view) => avatar.reach(targets, view),
5076
+ strike: (clipId, strikeOptions) => {
5077
+ const clip = strikes.get(clipId);
5078
+ if (!clip) return null;
5079
+ const left = strikeOptions?.leg === "left" || strikeOptions?.hand === "left";
5080
+ const punch = guard !== void 0 && isPunch(clip);
5081
+ avatar.play(left ? leftOf(clip) : clip, punch ? { blend: punchBlend } : void 0);
5082
+ strikeLeft = punch ? clip.duration : 0;
5083
+ guardLeft = punch ? guardHold : 0;
5084
+ guarding = false;
5085
+ return impactOf(clip);
5086
+ }
5087
+ };
5088
+ };
5089
+
5090
+ // src/player/test-module.ts
5091
+ import { POSE_CLIPS as POSE_CLIPS2, POSE_CLIP_LOCALES } from "@adatechnology/game-characters";
5092
+
5093
+ // src/player/test-text.ts
5094
+ var AVATAR_TEXT = {
5095
+ en: {
5096
+ group: "Avatar",
5097
+ name: "Player body",
5098
+ actions: {
5099
+ camera_first: "Camera: 1st person",
5100
+ camera_third: "Camera: 3rd person",
5101
+ crouch: "Crouch",
5102
+ stand: "Stand up",
5103
+ fall: "Fall down",
5104
+ get_up: "Get up",
5105
+ armed_on: "Armed: on",
5106
+ armed_off: "Armed: off",
5107
+ stop_clip: "Stop clip"
5108
+ },
5109
+ play: "Play",
5110
+ metrics: { camera: "Camera", clip: "Clip playing", last_event: "Last clip event", state: "State", joints: "Joints" },
5111
+ none: "none"
5112
+ },
5113
+ "pt-BR": {
5114
+ group: "Avatar",
5115
+ name: "Corpo do jogador",
5116
+ actions: {
5117
+ camera_first: "C\xE2mera: 1\xAA pessoa",
5118
+ camera_third: "C\xE2mera: 3\xAA pessoa",
5119
+ crouch: "Agachar",
5120
+ stand: "Levantar",
5121
+ fall: "Cair",
5122
+ get_up: "Levantar do ch\xE3o",
5123
+ armed_on: "Armado: ligado",
5124
+ armed_off: "Armado: desligado",
5125
+ stop_clip: "Parar clipe"
5126
+ },
5127
+ play: "Tocar",
5128
+ metrics: { camera: "C\xE2mera", clip: "Clipe tocando", last_event: "\xDAltimo evento do clipe", state: "Estado", joints: "Juntas" },
5129
+ none: "nenhum"
5130
+ }
5131
+ };
5132
+
5133
+ // src/player/test-module.ts
5134
+ var AVATAR_AREA_ID = "avatar_area";
5135
+ var round = (value) => (Math.round(value * 100) / 100).toString();
5136
+ function createAvatarTestModule(options) {
5137
+ const clips = options.clips ?? POSE_CLIPS2;
5138
+ const textOf = (locale) => AVATAR_TEXT[locale ?? options.locale ?? "pt-BR"] ?? AVATAR_TEXT["pt-BR"];
5139
+ const phaseLabel = (label, locale) => {
5140
+ const id = locale ?? options.locale ?? "pt-BR";
5141
+ const table = { ...POSE_CLIP_LOCALES[id] ?? POSE_CLIP_LOCALES["pt-BR"], ...options.clipLocales?.[id] ?? options.clipLocales?.["pt-BR"] };
5142
+ return typeof label === "string" ? table[label] ?? label : "";
5143
+ };
5144
+ const state = { crouch: false, down: false, armed: null };
5145
+ let host = null;
5146
+ let lastEvent = "";
5147
+ const log = (action, extra = {}) => {
5148
+ const entry = { type: "avatar", action, ...extra };
5149
+ host?.log(JSON.stringify(entry), entry);
5150
+ };
5151
+ const push = (action, patch, extra = {}) => {
5152
+ Object.assign(state, patch);
5153
+ options.state?.set(patch);
5154
+ log(action, { crouch: state.crouch, down: state.down, armed: state.armed, ...extra });
5155
+ };
5156
+ const setCamera = (mode) => {
5157
+ options.camera?.set(mode);
5158
+ log(`camera_${mode}`, { mode: options.camera?.mode() ?? null });
5159
+ };
5160
+ const onChange = (change) => {
5161
+ if (change.kind !== "action") return;
5162
+ switch (change.id) {
5163
+ case "camera_first":
5164
+ return setCamera("first");
5165
+ case "camera_third":
5166
+ return setCamera("third");
5167
+ case "crouch":
5168
+ return push("crouch", { crouch: true });
5169
+ case "stand":
5170
+ return push("stand", { crouch: false });
5171
+ case "fall":
5172
+ return push("fall", { down: true });
5173
+ case "get_up":
5174
+ return push("get_up", { down: false });
5175
+ case "armed_on":
5176
+ return push("armed_on", { armed: true });
5177
+ case "armed_off":
5178
+ return push("armed_off", { armed: false });
5179
+ case "stop_clip":
5180
+ options.avatar.stop();
5181
+ return log("stop_clip", { playing: options.avatar.playing });
5182
+ default: {
5183
+ const clip = clips.find((item) => `play_${item.id}` === change.id);
5184
+ if (!clip) return;
5185
+ options.avatar.play(clip);
5186
+ log("play_clip", { clip: clip.id, duration: clip.duration, loop: clip.loop ?? false, channels: clip.channels ?? "full" });
5187
+ }
5188
+ }
5189
+ };
5190
+ const joints = () => {
5191
+ const rig = options.avatar.rig;
5192
+ const [armA, armB] = rig.arms;
5193
+ const [legA, legB] = rig.legs;
5194
+ return `arms ${round(armA.g.rotation.x)}/${round(armB.g.rotation.x)} elbows ${round(armA.el.rotation.x)}/${round(armB.el.rotation.x)} legs ${round(legA.g.rotation.x)}/${round(legB.g.rotation.x)} knees ${round(legA.kn.rotation.x)}/${round(legB.kn.rotation.x)} lean ${round(rig.upper.rotation.x)}`;
5195
+ };
5196
+ return {
5197
+ id: AVATAR_AREA_ID,
5198
+ name: textOf().name,
5199
+ ground: { surface: "concrete_slab" },
5200
+ group: textOf().group,
5201
+ state,
5202
+ items: () => [],
5203
+ area: () => ({ placements: [], view: { eye: [0, 1.7, 6], target: [0, 1.2, 0] } }),
5204
+ controls: (locale) => {
5205
+ const text = textOf(locale);
5206
+ const action = (id, name, hint) => ({ kind: "action", id, name, ...hint ? { hint } : {} });
5207
+ return [
5208
+ action("camera_first", text.actions.camera_first),
5209
+ action("camera_third", text.actions.camera_third),
5210
+ action("crouch", text.actions.crouch),
5211
+ action("stand", text.actions.stand),
5212
+ action("fall", text.actions.fall),
5213
+ action("get_up", text.actions.get_up),
5214
+ action("armed_on", text.actions.armed_on),
5215
+ action("armed_off", text.actions.armed_off),
5216
+ ...clips.map((clip) => action(`play_${clip.id}`, `${text.play}: ${clip.id}`)),
5217
+ action("stop_clip", text.actions.stop_clip)
5218
+ ];
5219
+ },
5220
+ metrics: (locale) => {
5221
+ const text = textOf(locale);
5222
+ return [
5223
+ { id: "camera", name: text.metrics.camera, read: () => options.camera?.mode() ?? text.none },
5224
+ { id: "clip", name: text.metrics.clip, read: () => options.avatar.playing ?? text.none },
5225
+ { id: "last_event", name: text.metrics.last_event, read: () => lastEvent || text.none },
5226
+ { id: "state", name: text.metrics.state, read: () => `${state.crouch ? "crouch " : ""}${state.down ? "down " : ""}${state.armed === null ? "" : state.armed ? "armed" : "unarmed"}`.trim() || text.none },
5227
+ { id: "joints", name: text.metrics.joints, read: joints }
5228
+ ];
5229
+ },
5230
+ attach(lent) {
5231
+ host = lent;
5232
+ const stopChange = lent.bench?.onChange(onChange);
5233
+ const stopEvents = options.avatar.on("event", (event) => {
5234
+ lastEvent = `${event.clip} ${event.type} ${round(event.t)}s${event.data && "label" in event.data ? ` ${phaseLabel(event.data.label)}` : ""}`;
5235
+ log("clip_event", { clip: event.clip, event: event.type, t: event.t, data: event.data ?? null });
5236
+ if (event.type === "impact") log("impact", { clip: event.clip, t: event.t, kind: event.data?.kind ?? null });
5237
+ });
5238
+ return () => {
5239
+ stopChange?.();
5240
+ stopEvents();
5241
+ host = null;
5242
+ };
5243
+ }
5244
+ };
5245
+ }
4816
5246
 
4817
5247
  // src/first-person/arms.ts
4818
5248
  import { clipEventsBetween as clipEventsBetween2, sampleFirstPersonClip } from "@adatechnology/game-characters";
@@ -4827,7 +5257,9 @@ var first_person_arms_default = {
4827
5257
  shoulder: [0.16, -0.36, 0.18],
4828
5258
  upper: 0.32,
4829
5259
  fore: 0.3,
4830
- reachShift: 0.35,
5260
+ reachShift: 0.42,
5261
+ reachBend: [1.35, 0.95],
5262
+ elbowView: [[0.96, -0.26, -0.06], null],
4831
5263
  wrist: 0.07,
4832
5264
  elbowPole: [0.45, -1, 0.2],
4833
5265
  shoulderLeft: [-0.07, -0.46, 0.18],
@@ -4854,22 +5286,14 @@ var first_person_arms_default = {
4854
5286
  };
4855
5287
 
4856
5288
  // 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 };
5289
+ var PALM = { radius: 0.048, scale: [0.6, 1.05, 1], down: 0.035 };
5290
+ var CURL = { closed: 1.25, near: 0.9, far: 1.1 };
5291
+ var THUMB = { open: 0.32, closed: 0.25 };
4867
5292
  var createHandMesh = (THREE, skin, wristSkin = skin) => {
4868
5293
  const root = new THREE.Group();
4869
5294
  root.name = "first-person-hand";
4870
5295
  const geometries = [];
4871
- const box = (parent, sx, sy, sz, x, y, z, material = skin) => {
4872
- const geometry = new THREE.BoxGeometry(sx, sy, sz);
5296
+ const add = (parent, geometry, material, x = 0, y = 0, z = 0) => {
4873
5297
  geometries.push(geometry);
4874
5298
  const mesh = new THREE.Mesh(geometry, material);
4875
5299
  mesh.position.set(x, y, z);
@@ -4877,64 +5301,26 @@ var createHandMesh = (THREE, skin, wristSkin = skin) => {
4877
5301
  parent.add(mesh);
4878
5302
  return mesh;
4879
5303
  };
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);
5304
+ const group = new THREE.Group();
5305
+ 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)));
5306
+ group.position.set(-PALM.down, 0, 0);
5307
+ root.add(group);
5308
+ add(group, new THREE.SphereGeometry(PALM.radius, 14, 10), skin, 0, -PALM.down, 0).scale.set(...PALM.scale);
5309
+ const built = buildHand({ THREE, add, hg: group, handM: skin, s: -1, detailed: true });
5310
+ const wristGeometry = new THREE.CylinderGeometry(0.03, 0.033, 0.05, 10);
4891
5311
  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);
5312
+ add(root, wristGeometry, wristSkin, -PALM.down - 0.03, 0, 0);
4923
5313
  let lastGrip = 0;
4924
5314
  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);
5315
+ const curlFinger = (finger, k) => {
5316
+ built.fingers[finger * 2].rotation.z = k * CURL.closed * CURL.near;
5317
+ built.fingers[finger * 2 + 1].rotation.z = k * CURL.closed * CURL.far;
4931
5318
  };
5319
+ const applyIndex = () => curlFinger(0, indexCurl === void 0 ? lastGrip : Math.min(1, Math.max(0, indexCurl)));
4932
5320
  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;
5321
+ lastGrip = Math.min(1, Math.max(0, grip));
5322
+ for (let finger = 1; finger < 4; finger++) curlFinger(finger, lastGrip);
5323
+ if (built.th) built.th.rotation.z = THUMB.open + lastGrip * THUMB.closed;
4938
5324
  applyIndex();
4939
5325
  };
4940
5326
  const setIndex = (value) => {
@@ -4956,6 +5342,8 @@ var createHandMesh = (THREE, skin, wristSkin = skin) => {
4956
5342
  var DEFAULT_BLEND = 0.18;
4957
5343
  var ELBOW_SEGMENTS = 8;
4958
5344
  var REACH_SLACK = 0.97;
5345
+ var ELBOW_VIEW_MARGIN = 0.86;
5346
+ var ELBOW_VIEW_PASSES = 4;
4959
5347
  var CONTACT_PROBE = 1.4;
4960
5348
  var CONTACT_MIN = 0.12;
4961
5349
  var CONTACT_MARGIN = 5e-3;
@@ -5104,6 +5492,11 @@ function createFirstPersonArms(THREE, options = {}) {
5104
5492
  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
5493
  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
5494
  const kneePole = new THREE.Vector3(...rig.kneePole);
5495
+ const reachOf = (sign) => {
5496
+ const bend = rig.reachBend?.[sign === 1 ? 0 : 1];
5497
+ if (bend === void 0) return (rig.upper + rig.fore) * REACH_SLACK;
5498
+ return Math.sqrt(rig.upper * rig.upper + rig.fore * rig.fore + 2 * rig.upper * rig.fore * Math.cos(bend));
5499
+ };
5107
5500
  const hip = new THREE.Vector3(...rig.hip);
5108
5501
  const euler = new THREE.Euler(0, 0, 0, "YZX");
5109
5502
  const quaternionOf = (r) => new THREE.Quaternion().setFromEuler(euler.set(r[0], r[1], r[2], "YZX"));
@@ -5193,15 +5586,45 @@ function createFirstPersonArms(THREE, options = {}) {
5193
5586
  out.scale = base.scale;
5194
5587
  return out;
5195
5588
  };
5196
- const drawArm = (arm, pose) => {
5589
+ const elbowAt = new THREE.Vector3();
5590
+ const viewOf = (camera) => {
5591
+ const lens = camera;
5592
+ if (!lens.isPerspectiveCamera) return null;
5593
+ const tanV = Math.tan(lens.fov * Math.PI / 360);
5594
+ return { tanV, tanH: tanV * lens.aspect };
5595
+ };
5596
+ const placeElbowInView = (sign, view) => {
5597
+ const way = rig.elbowView?.[sign === 1 ? 0 : 1];
5598
+ if (!way) return false;
5599
+ elbowAt.set(way[0], way[1], way[2]).setLength(rig.fore).add(wristAt);
5600
+ for (let pass = 0; pass < ELBOW_VIEW_PASSES; pass++) {
5601
+ const depth = Math.max(0.05, -elbowAt.z);
5602
+ const high = view.tanV * ELBOW_VIEW_MARGIN * depth;
5603
+ const wide = view.tanH * ELBOW_VIEW_MARGIN * depth;
5604
+ elbowAt.y = Math.max(elbowAt.y, -high);
5605
+ elbowAt.x = Math.min(wide, Math.max(-wide, elbowAt.x));
5606
+ elbowAt.sub(wristAt).setLength(rig.fore).add(wristAt);
5607
+ }
5608
+ shoulder.sub(elbowAt).setLength(rig.upper).add(elbowAt);
5609
+ return true;
5610
+ };
5611
+ const drawArm = (arm, pose, view) => {
5197
5612
  copyPose(arm.drawn, pose);
5198
5613
  const scale = pose.scale;
5199
5614
  fingers.set(1, 0, 0).applyQuaternion(pose.q);
5200
5615
  wristAt.copy(pose.p).addScaledVector(fingers, -rig.wrist * scale);
5201
5616
  shoulderOf(arm.sign, shoulder).add(bobOffset);
5202
- const excess = shoulder.distanceTo(wristAt) - (rig.upper + rig.fore) * REACH_SLACK;
5617
+ if (view && placeElbowInView(arm.sign, view)) {
5618
+ solveTwoBone(shoulder, wristAt, rig.upper, rig.fore, towards.copy(elbowAt).sub(shoulder).add(elbowAt).sub(wristAt), bone);
5619
+ finishArm(arm, pose, scale);
5620
+ return;
5621
+ }
5622
+ const excess = shoulder.distanceTo(wristAt) - reachOf(arm.sign);
5203
5623
  if (excess > 0 && rig.reachShift > 0) shoulder.addScaledVector(towards.subVectors(wristAt, shoulder).normalize(), Math.min(excess, rig.reachShift));
5204
5624
  solveTwoBone(shoulder, wristAt, rig.upper, rig.fore, poleOf(arm.sign), bone);
5625
+ finishArm(arm, pose, scale);
5626
+ };
5627
+ const finishArm = (arm, pose, scale) => {
5205
5628
  palmAt.copy(bone.end).addScaledVector(fingers, rig.wrist * scale);
5206
5629
  arm.drawn.p.copy(palmAt);
5207
5630
  arm.anchor.position.copy(palmAt);
@@ -5313,7 +5736,7 @@ function createFirstPersonArms(THREE, options = {}) {
5313
5736
  dropped.p.y -= AWAY_DROP * ease(arm.away);
5314
5737
  pose = dropped;
5315
5738
  }
5316
- drawArm(arm, pose);
5739
+ drawArm(arm, pose, mode === "target" && targets?.elbows !== "body" ? viewOf(state.camera) : null);
5317
5740
  const shown = arm.away < AWAY_HIDDEN;
5318
5741
  for (const part of [arm.anchor, arm.upper, arm.fore, arm.cuff, arm.elbow]) part.visible = shown;
5319
5742
  }
@@ -5355,240 +5778,6 @@ function createFirstPersonArms(THREE, options = {}) {
5355
5778
  }
5356
5779
  };
5357
5780
  }
5358
-
5359
- // src/player/room-avatar.ts
5360
- var impactOf = (clip) => clip.events?.find((event) => event.type === "impact")?.t ?? null;
5361
- var isPunch = (clip) => clip.events?.some((event) => event.type === "impact" && event.data?.kind === "punch") ?? false;
5362
- var GUARD_SPEED = 0.3;
5363
- var toRoomAvatar = (avatar, options = {}) => {
5364
- const arms = options.firstPerson === false ? null : createFirstPersonArms(avatar.three, {
5365
- 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
- clips: options.firstPersonClips ?? FIRST_PERSON_CLIPS,
5367
- hideWhenIdle: true,
5368
- ...options.firstPerson
5369
- });
5370
- const strikes = new Map((options.strikeClips ?? ATTACK_CLIPS).map((clip) => [clip.id, clip]));
5371
- const mirrored = /* @__PURE__ */ new Map();
5372
- const leftOf = (clip) => {
5373
- let found = mirrored.get(clip.id);
5374
- if (!found) mirrored.set(clip.id, found = mirrorPoseClip(clip, `${clip.id}_left`));
5375
- return found;
5376
- };
5377
- const guard = options.guardClip === false ? void 0 : strikes.get(options.guardClip ?? "fight_guard");
5378
- const guardHold = options.guardHold ?? 1.2;
5379
- const punchBlend = options.punchBlend ?? 0.05;
5380
- let strikeLeft = 0;
5381
- let guardLeft = 0;
5382
- let guarding = false;
5383
- const stepGuard = (dt, state) => {
5384
- if (!guard) return;
5385
- const free = state.speed < GUARD_SPEED && !state.down && !state.armed && !state.holding && !state.push && !state.itemUse && !state.running;
5386
- if (strikeLeft > 0) {
5387
- strikeLeft -= dt;
5388
- if (strikeLeft <= 0 && guardLeft > 0 && free) {
5389
- avatar.play(guard);
5390
- guarding = true;
5391
- }
5392
- return;
5393
- }
5394
- guardLeft = Math.max(0, guardLeft - dt);
5395
- if (guarding && (guardLeft <= 0 || !free || avatar.playing !== guard.id)) {
5396
- if (avatar.playing === guard.id) avatar.stop();
5397
- guarding = false;
5398
- }
5399
- };
5400
- return {
5401
- root: avatar.root,
5402
- avatar,
5403
- arms,
5404
- ...arms ? { firstPerson: { root: arms.root, update: (dt, state) => arms.update(dt, state) } } : {},
5405
- update: (dt, state) => {
5406
- stepGuard(dt, state);
5407
- avatar.update(dt, {
5408
- speed: state.speed,
5409
- running: state.running,
5410
- crouch: state.crouch,
5411
- down: state.down,
5412
- armed: state.armed,
5413
- pitch: -state.pitch,
5414
- ...state.holding !== void 0 ? { holding: state.holding } : {},
5415
- ...state.itemUse !== void 0 ? { itemUse: state.itemUse } : {},
5416
- ...state.push !== void 0 ? { push: state.push } : {}
5417
- });
5418
- },
5419
- hand: (side) => avatar.hand(side),
5420
- reach: (targets) => avatar.reach(targets),
5421
- strike: (clipId, strikeOptions) => {
5422
- const clip = strikes.get(clipId);
5423
- if (!clip) return null;
5424
- const left = strikeOptions?.leg === "left" || strikeOptions?.hand === "left";
5425
- const punch = guard !== void 0 && isPunch(clip);
5426
- avatar.play(left ? leftOf(clip) : clip, punch ? { blend: punchBlend } : void 0);
5427
- strikeLeft = punch ? clip.duration : 0;
5428
- guardLeft = punch ? guardHold : 0;
5429
- guarding = false;
5430
- arms?.play(clipId, strikeOptions?.leg === "left" ? { leg: "left" } : strikeOptions?.hand === "left" ? { hand: "left" } : void 0);
5431
- return impactOf(clip);
5432
- }
5433
- };
5434
- };
5435
-
5436
- // src/player/test-module.ts
5437
- import { POSE_CLIPS as POSE_CLIPS2, POSE_CLIP_LOCALES } from "@adatechnology/game-characters";
5438
-
5439
- // src/player/test-text.ts
5440
- var AVATAR_TEXT = {
5441
- en: {
5442
- group: "Avatar",
5443
- name: "Player body",
5444
- actions: {
5445
- camera_first: "Camera: 1st person",
5446
- camera_third: "Camera: 3rd person",
5447
- crouch: "Crouch",
5448
- stand: "Stand up",
5449
- fall: "Fall down",
5450
- get_up: "Get up",
5451
- armed_on: "Armed: on",
5452
- armed_off: "Armed: off",
5453
- stop_clip: "Stop clip"
5454
- },
5455
- play: "Play",
5456
- metrics: { camera: "Camera", clip: "Clip playing", last_event: "Last clip event", state: "State", joints: "Joints" },
5457
- none: "none"
5458
- },
5459
- "pt-BR": {
5460
- group: "Avatar",
5461
- name: "Corpo do jogador",
5462
- actions: {
5463
- camera_first: "C\xE2mera: 1\xAA pessoa",
5464
- camera_third: "C\xE2mera: 3\xAA pessoa",
5465
- crouch: "Agachar",
5466
- stand: "Levantar",
5467
- fall: "Cair",
5468
- get_up: "Levantar do ch\xE3o",
5469
- armed_on: "Armado: ligado",
5470
- armed_off: "Armado: desligado",
5471
- stop_clip: "Parar clipe"
5472
- },
5473
- play: "Tocar",
5474
- metrics: { camera: "C\xE2mera", clip: "Clipe tocando", last_event: "\xDAltimo evento do clipe", state: "Estado", joints: "Juntas" },
5475
- none: "nenhum"
5476
- }
5477
- };
5478
-
5479
- // src/player/test-module.ts
5480
- var AVATAR_AREA_ID = "avatar_area";
5481
- var round = (value) => (Math.round(value * 100) / 100).toString();
5482
- function createAvatarTestModule(options) {
5483
- const clips = options.clips ?? POSE_CLIPS2;
5484
- const textOf = (locale) => AVATAR_TEXT[locale ?? options.locale ?? "pt-BR"] ?? AVATAR_TEXT["pt-BR"];
5485
- const phaseLabel = (label, locale) => {
5486
- const id = locale ?? options.locale ?? "pt-BR";
5487
- const table = { ...POSE_CLIP_LOCALES[id] ?? POSE_CLIP_LOCALES["pt-BR"], ...options.clipLocales?.[id] ?? options.clipLocales?.["pt-BR"] };
5488
- return typeof label === "string" ? table[label] ?? label : "";
5489
- };
5490
- const state = { crouch: false, down: false, armed: null };
5491
- let host = null;
5492
- let lastEvent = "";
5493
- const log = (action, extra = {}) => {
5494
- const entry = { type: "avatar", action, ...extra };
5495
- host?.log(JSON.stringify(entry), entry);
5496
- };
5497
- const push = (action, patch, extra = {}) => {
5498
- Object.assign(state, patch);
5499
- options.state?.set(patch);
5500
- log(action, { crouch: state.crouch, down: state.down, armed: state.armed, ...extra });
5501
- };
5502
- const setCamera = (mode) => {
5503
- options.camera?.set(mode);
5504
- log(`camera_${mode}`, { mode: options.camera?.mode() ?? null });
5505
- };
5506
- const onChange = (change) => {
5507
- if (change.kind !== "action") return;
5508
- switch (change.id) {
5509
- case "camera_first":
5510
- return setCamera("first");
5511
- case "camera_third":
5512
- return setCamera("third");
5513
- case "crouch":
5514
- return push("crouch", { crouch: true });
5515
- case "stand":
5516
- return push("stand", { crouch: false });
5517
- case "fall":
5518
- return push("fall", { down: true });
5519
- case "get_up":
5520
- return push("get_up", { down: false });
5521
- case "armed_on":
5522
- return push("armed_on", { armed: true });
5523
- case "armed_off":
5524
- return push("armed_off", { armed: false });
5525
- case "stop_clip":
5526
- options.avatar.stop();
5527
- return log("stop_clip", { playing: options.avatar.playing });
5528
- default: {
5529
- const clip = clips.find((item) => `play_${item.id}` === change.id);
5530
- if (!clip) return;
5531
- options.avatar.play(clip);
5532
- log("play_clip", { clip: clip.id, duration: clip.duration, loop: clip.loop ?? false, channels: clip.channels ?? "full" });
5533
- }
5534
- }
5535
- };
5536
- const joints = () => {
5537
- const rig = options.avatar.rig;
5538
- const [armA, armB] = rig.arms;
5539
- const [legA, legB] = rig.legs;
5540
- return `arms ${round(armA.g.rotation.x)}/${round(armB.g.rotation.x)} elbows ${round(armA.el.rotation.x)}/${round(armB.el.rotation.x)} legs ${round(legA.g.rotation.x)}/${round(legB.g.rotation.x)} knees ${round(legA.kn.rotation.x)}/${round(legB.kn.rotation.x)} lean ${round(rig.upper.rotation.x)}`;
5541
- };
5542
- return {
5543
- id: AVATAR_AREA_ID,
5544
- name: textOf().name,
5545
- ground: { surface: "concrete_slab" },
5546
- group: textOf().group,
5547
- state,
5548
- items: () => [],
5549
- area: () => ({ placements: [], view: { eye: [0, 1.7, 6], target: [0, 1.2, 0] } }),
5550
- controls: (locale) => {
5551
- const text = textOf(locale);
5552
- const action = (id, name, hint) => ({ kind: "action", id, name, ...hint ? { hint } : {} });
5553
- return [
5554
- action("camera_first", text.actions.camera_first),
5555
- action("camera_third", text.actions.camera_third),
5556
- action("crouch", text.actions.crouch),
5557
- action("stand", text.actions.stand),
5558
- action("fall", text.actions.fall),
5559
- action("get_up", text.actions.get_up),
5560
- action("armed_on", text.actions.armed_on),
5561
- action("armed_off", text.actions.armed_off),
5562
- ...clips.map((clip) => action(`play_${clip.id}`, `${text.play}: ${clip.id}`)),
5563
- action("stop_clip", text.actions.stop_clip)
5564
- ];
5565
- },
5566
- metrics: (locale) => {
5567
- const text = textOf(locale);
5568
- return [
5569
- { id: "camera", name: text.metrics.camera, read: () => options.camera?.mode() ?? text.none },
5570
- { id: "clip", name: text.metrics.clip, read: () => options.avatar.playing ?? text.none },
5571
- { id: "last_event", name: text.metrics.last_event, read: () => lastEvent || text.none },
5572
- { id: "state", name: text.metrics.state, read: () => `${state.crouch ? "crouch " : ""}${state.down ? "down " : ""}${state.armed === null ? "" : state.armed ? "armed" : "unarmed"}`.trim() || text.none },
5573
- { id: "joints", name: text.metrics.joints, read: joints }
5574
- ];
5575
- },
5576
- attach(lent) {
5577
- host = lent;
5578
- const stopChange = lent.bench?.onChange(onChange);
5579
- const stopEvents = options.avatar.on("event", (event) => {
5580
- lastEvent = `${event.clip} ${event.type} ${round(event.t)}s${event.data && "label" in event.data ? ` ${phaseLabel(event.data.label)}` : ""}`;
5581
- log("clip_event", { clip: event.clip, event: event.type, t: event.t, data: event.data ?? null });
5582
- if (event.type === "impact") log("impact", { clip: event.clip, t: event.t, kind: event.data?.kind ?? null });
5583
- });
5584
- return () => {
5585
- stopChange?.();
5586
- stopEvents();
5587
- host = null;
5588
- };
5589
- }
5590
- };
5591
- }
5592
5781
  export {
5593
5782
  AVATAR_AREA_ID,
5594
5783
  BAKE_NEUTRAL_POSE,