@adatechnology/game-characters-three 0.2.1 → 0.3.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
@@ -75,6 +75,7 @@ var HIP_PIVOT = "vec3(0.,0.95,0.)";
75
75
  var createBodyChunk = ({ hipHeight = 0.95 } = {}) => hipHeight === 0.95 ? BODY_CHUNK : BODY_CHUNK.replaceAll(HIP_PIVOT, `vec3(0.,${hipHeight},0.)`);
76
76
 
77
77
  // src/instanced/body-geometry.ts
78
+ import { shapeGeometries } from "@adatechnology/game-shapes-three";
78
79
  import { TEAM_GAME } from "@adatechnology/game-characters";
79
80
  var negativeFirst = (first, second) => first[0] <= second[0] ? [first, second] : [second, first];
80
81
  var jointsOf = (body) => {
@@ -124,7 +125,8 @@ var mergeBody = (list, three) => {
124
125
  merged.computeBoundingSphere();
125
126
  return merged;
126
127
  };
127
- var buildInstancedBody = (three, { segments, body = TEAM_GAME }) => {
128
+ var buildInstancedBody = (host, { segments, body = TEAM_GAME }) => {
129
+ const three = shapeGeometries(host);
128
130
  const joints = jointsOf(body);
129
131
  const parts = [];
130
132
  const half = Math.max(4, segments >> 1);
@@ -299,13 +301,15 @@ var LOD_CASTS_SHADOW = [true, true, false];
299
301
  var lodAt = (distance2, distances = LOD_DISTANCES) => distance2 < distances[0] ? 0 : distance2 < distances[1] ? 1 : 2;
300
302
 
301
303
  // src/instanced/bake.ts
304
+ import { shapeGeometries as shapeGeometries2 } from "@adatechnology/game-shapes-three";
302
305
  var BAKE_NEUTRAL_POSE = { lean: 0, armL: 0, armR: 0, elbL: 0, elbR: 0, legL: 0, legR: 0, kneeL: 0, kneeR: 0, headX: 0, headZ: 0, drop: 0, armOut: 0, neck: 0, headFwd: 0 };
303
306
  var BLOOD_LIFT_NEAR = 6e-3;
304
307
  var BLOOD_LIFT_FAR = 0.012;
305
308
  var FAR_LOD = 0.6;
306
309
  var EYE_SCALE = 2.2;
307
310
  var EYE_DEPTH_SCALE = 1.4;
308
- var bakeBody = (three, builders, spec, lod) => {
311
+ var bakeBody = (host, builders, spec, lod) => {
312
+ const three = shapeGeometries2(host);
309
313
  const root = spec.retrato && builders.buildPortrait ? builders.buildPortrait(spec.retrato, lod) : builders.buildBest({ ...spec, pose: { ...BAKE_NEUTRAL_POSE, ...spec.pose ?? {} } }, lod);
310
314
  const rig = root.userData.rig;
311
315
  const P = rig.P;
@@ -448,6 +452,7 @@ var bakeVariants = (three, builders, types) => {
448
452
  var createVariantBatches = (three, variant, time) => variant.geometries.map((geometry, level) => createBodyBatch(three, geometry, { max: LOD_BATCH_CAPACITY[level], castShadow: LOD_CASTS_SHADOW[level], time }));
449
453
 
450
454
  // src/instanced/flying-limbs.ts
455
+ import { shapeGeometries as shapeGeometries3 } from "@adatechnology/game-shapes-three";
451
456
  var GRAVITY = 9.8;
452
457
  var FLOOR_Y = 0.07;
453
458
  var BOUNCE = -0.3;
@@ -455,7 +460,8 @@ var FRICTION = 0.6;
455
460
  var SPIN_DAMPING = 0.5;
456
461
  var REST_SPEED = 0.4;
457
462
  var LEG_COLOR = 2896704;
458
- var createFlyingLimbs = (three, { max = 40, random, effects = {} }) => {
463
+ var createFlyingLimbs = (host, { max = 40, random, effects = {} }) => {
464
+ const three = shapeGeometries3(host);
459
465
  const limbs = [];
460
466
  const mesh = new three.InstancedMesh(new three.CapsuleGeometry(0.065, 0.32, 2, 6), new three.MeshLambertMaterial({ color: 16777215 }), max);
461
467
  mesh.count = 0;
@@ -642,8 +648,10 @@ var createRigAnimator = (options = {}) => {
642
648
  };
643
649
 
644
650
  // src/rich/best-builder.ts
651
+ import { shapeGeometries as shapeGeometries4 } from "@adatechnology/game-shapes-three";
645
652
  import { createBloodStream, createRng, drawAppearance } from "@adatechnology/game-characters";
646
- var createBestBuilder = (THREE, { types, random = Math.random }) => {
653
+ var createBestBuilder = (host, { types, random = Math.random }) => {
654
+ const THREE = shapeGeometries4(host);
647
655
  const options = { types, random };
648
656
  let LK = 1, topM_ref = null;
649
657
  const SPH = (r, w = 8, h = 6, ...a) => new THREE.SphereGeometry(r, Math.max(4, Math.round(w * LK)), Math.max(3, Math.round(h * LK)), ...a);
@@ -1208,6 +1216,7 @@ var createBestBuilder = (THREE, { types, random = Math.random }) => {
1208
1216
  };
1209
1217
 
1210
1218
  // src/rich/person-builder.ts
1219
+ import { shapeGeometries as shapeGeometries6 } from "@adatechnology/game-shapes-three";
1211
1220
  import { MOVES, createRng as createRng2 } from "@adatechnology/game-characters";
1212
1221
 
1213
1222
  // src/rich/accessories/face-features.ts
@@ -1245,8 +1254,8 @@ var FACE_FEATURES = [
1245
1254
  },
1246
1255
  {
1247
1256
  ids: ["stubble"],
1248
- apply: ({ Hd, THREE, add, cmat, scaleHex, sp }) => {
1249
- const b = add(Hd, new THREE.SphereGeometry(0.123, 16, 12), cmat(scaleHex(sp.skin, 0.62)), 0, 1.705, 0.032);
1257
+ apply: ({ Hd, THREE, add, cmat, scaleHex: scaleHex2, sp }) => {
1258
+ const b = add(Hd, new THREE.SphereGeometry(0.123, 16, 12), cmat(scaleHex2(sp.skin, 0.62)), 0, 1.705, 0.032);
1250
1259
  b.scale.set(1, 0.74, 1);
1251
1260
  }
1252
1261
  },
@@ -1938,9 +1947,11 @@ EXTRAS.rifle_tape = EXTRAS.rifle_front;
1938
1947
  EXTRAS.dbl_front = EXTRAS.rifle_front;
1939
1948
 
1940
1949
  // src/rich/person-kit.ts
1941
- var createPersonKit = (THREE, { random: R, createCanvas = () => document.createElement("canvas") }) => {
1950
+ import { shapeGeometries as shapeGeometries5 } from "@adatechnology/game-shapes-three";
1951
+ var createPersonKit = (host, { random: R, createCanvas = () => document.createElement("canvas") }) => {
1952
+ const THREE = shapeGeometries5(host);
1942
1953
  const T = THREE;
1943
- const scaleHex = (hex2, factor) => {
1954
+ const scaleHex2 = (hex2, factor) => {
1944
1955
  const channel = (shift) => (hex2 >> shift & 255) / 255 * factor * 255;
1945
1956
  return channel(16) << 16 ^ channel(8) << 8 ^ channel(0) << 0;
1946
1957
  };
@@ -2050,7 +2061,7 @@ var createPersonKit = (THREE, { random: R, createCanvas = () => document.createE
2050
2061
  return o;
2051
2062
  };
2052
2063
  const sy = 0.8 + R() * 0.4, seed = R() * 10;
2053
- const core = skin ? stainM(color, { roughness: 0.12, metalness: 0.1 }) : stainM(scaleHex(color, 0.72));
2064
+ const core = skin ? stainM(color, { roughness: 0.12, metalness: 0.1 }) : stainM(scaleHex2(color, 0.72));
2054
2065
  const a = push(add(parent, blobGeo(blobR(40, skin ? 0.4 : 0.9, seed), r * 0.82, r * 0.82 * sy), core, x, y, z + 4e-3));
2055
2066
  if (!skin) {
2056
2067
  push(add(parent, blobGeo(blobR(48, 1.6, seed + 0.3), r * 1.3, r * 1.3 * sy), stainM(color, { transparent: true, opacity: 0.5, depthWrite: false }), x, y, z + 2e-3));
@@ -2070,14 +2081,15 @@ var createPersonKit = (THREE, { random: R, createCanvas = () => document.createE
2070
2081
  }
2071
2082
  return a;
2072
2083
  }
2073
- return { THREE, PI, SEG, R, scaleHex, Bx, add, cmat, ball, ring, cap, limb, mlimb, torsoGeo, splat, stainM, chessTex };
2084
+ return { THREE, PI, SEG, R, scaleHex: scaleHex2, Bx, add, cmat, ball, ring, cap, limb, mlimb, torsoGeo, splat, stainM, chessTex };
2074
2085
  };
2075
2086
 
2076
2087
  // src/rich/person-builder.ts
2077
2088
  var PAGE_EYES = { lento: 14926657, corredor: 15022379, brutamonte: 15698474 };
2078
- var createPersonBuilder = (THREE, options = {}) => {
2089
+ var createPersonBuilder = (host, options = {}) => {
2090
+ const THREE = shapeGeometries6(host);
2079
2091
  const kit = createPersonKit(THREE, { random: options.random ?? createRng2(77), createCanvas: options.createCanvas });
2080
- const { PI, R, scaleHex, Bx, add, cmat, ball, ring, cap, limb, mlimb, torsoGeo, splat, stainM, chessTex } = kit;
2092
+ const { PI, R, scaleHex: scaleHex2, Bx, add, cmat, ball, ring, cap, limb, mlimb, torsoGeo, splat, stainM, chessTex } = kit;
2081
2093
  const reduce = options.reducedMotion ?? false;
2082
2094
  const detailed = (options.detail ?? 0) === 0;
2083
2095
  const EYE = { ...PAGE_EYES, ...options.zombieEyes };
@@ -2178,7 +2190,7 @@ var createPersonBuilder = (THREE, options = {}) => {
2178
2190
  const ridge = ball(Hd, 0.1, skinM, 0, 1.842, 0.105);
2179
2191
  ridge.scale.set(1.3, 0.28, 0.55);
2180
2192
  add(Hd, new THREE.SphereGeometry(0.021, 10, 8), skinM, 0, 1.762, 0.182);
2181
- add(Hd, Bx(0.064, 0.013, 0.02), cmat(scaleHex(sp.skin, 0.68)), 0, 1.703, 0.156);
2193
+ add(Hd, Bx(0.064, 0.013, 0.02), cmat(scaleHex2(sp.skin, 0.68)), 0, 1.703, 0.156);
2182
2194
  const bags = add(Hd, Bx(0.2, 0.022, 0.012), new THREE.MeshBasicMaterial({ color: 3811882, transparent: true, opacity: 0 }), 0, 1.79, 0.166);
2183
2195
  bags.castShadow = false;
2184
2196
  const hc = cmat(sp.hair.c), hs = sp.hair.s;
@@ -2215,7 +2227,7 @@ var createPersonBuilder = (THREE, options = {}) => {
2215
2227
  t.rotation.x = 0.25;
2216
2228
  }
2217
2229
  const f = sp.face || [];
2218
- for (const feature of FACE_FEATURES) if (feature.ids.some((id) => f.includes(id))) feature.apply({ THREE, PI, Hd, add, Bx, cmat, hc, sp, f, scaleHex });
2230
+ for (const feature of FACE_FEATURES) if (feature.ids.some((id) => f.includes(id))) feature.apply({ THREE, PI, Hd, add, Bx, cmat, hc, sp, f, scaleHex: scaleHex2 });
2219
2231
  if (sp.hat) {
2220
2232
  const k = sp.hat.k, c = cmat(sp.hat.c);
2221
2233
  HATS.find((hat) => hat.kinds.includes(k))?.apply({ THREE, PI, Hd, add, Bx, cmat, cap, c, k, sp });
@@ -2432,6 +2444,7 @@ var createPersonBuilder = (THREE, options = {}) => {
2432
2444
  };
2433
2445
 
2434
2446
  // src/eyes.ts
2447
+ import { shapeGeometries as shapeGeometries7 } from "@adatechnology/game-shapes-three";
2435
2448
  var GLOW_SIZE = 64;
2436
2449
  var createGlowTexture = (three, createCanvas) => {
2437
2450
  const make = createCanvas ?? (typeof document === "undefined" ? void 0 : () => document.createElement("canvas"));
@@ -2448,7 +2461,8 @@ var createGlowTexture = (three, createCanvas) => {
2448
2461
  context.fillRect(0, 0, GLOW_SIZE, GLOW_SIZE);
2449
2462
  return new three.CanvasTexture(canvas);
2450
2463
  };
2451
- var createEye = (three, { color, radius = 0.02, glow, glowOpacity = 0.55, texture = null }) => {
2464
+ var createEye = (host, { color, radius = 0.02, glow, glowOpacity = 0.55, texture = null }) => {
2465
+ const three = shapeGeometries7(host);
2452
2466
  const eye = new three.Group();
2453
2467
  const ball = new three.Mesh(new three.SphereGeometry(radius, 10, 8), new three.MeshBasicMaterial({ color }));
2454
2468
  ball.userData.eye = 1;
@@ -3436,6 +3450,59 @@ var personSpecOf = (profession) => {
3436
3450
  return spec;
3437
3451
  };
3438
3452
 
3453
+ // src/test-area/knock.ts
3454
+ import { blastShove } from "@adatechnology/game-contracts";
3455
+ var KNOCK_DOWN_SPEED = 2.5;
3456
+ var KNOCKED_DOWN_ANGLE = 1.35;
3457
+ var LIE_SECONDS = 1.6;
3458
+ var STAND_UP_PER_SPEED = 0.25;
3459
+ var FALL_SECONDS = 0.25;
3460
+ var RISE_SECONDS = 0.6;
3461
+ var GROUND_FRICTION = 7;
3462
+ var GRAVITY3 = 9.81;
3463
+ var CHEST = 1.1;
3464
+ var bodyMass = (height, build = 1) => 70 * (height / 1.75) ** 2 * build;
3465
+ function knockOf(blast, feet, mass, options) {
3466
+ const hit = blastShove(blast, { position: [feet[0], feet[1] + CHEST, feet[2]], mass }, options);
3467
+ if (!hit) return void 0;
3468
+ const [dx, dy, dz] = hit.direction;
3469
+ return { velocity: [dx * hit.speed, Math.max(0, dy * hit.speed), dz * hit.speed], airborne: dy > 0, lying: 0, lieLeft: 0, down: hit.speed >= KNOCK_DOWN_SPEED };
3470
+ }
3471
+ function stepKnock(knock, position, dt, groundAt, alive) {
3472
+ const v = knock.velocity;
3473
+ position.x += v[0] * dt;
3474
+ position.z += v[2] * dt;
3475
+ const ground = groundAt(position.x, position.z);
3476
+ if (knock.airborne || position.y > ground + 1e-4) {
3477
+ v[1] -= GRAVITY3 * dt;
3478
+ position.y += v[1] * dt;
3479
+ if (position.y <= ground) {
3480
+ position.y = ground;
3481
+ v[1] = 0;
3482
+ knock.airborne = false;
3483
+ }
3484
+ } else position.y = ground;
3485
+ const horizontal = Math.hypot(v[0], v[2]);
3486
+ if (!knock.airborne && horizontal > 0) {
3487
+ const slowed = Math.max(0, horizontal - GROUND_FRICTION * dt);
3488
+ v[0] *= slowed / horizontal;
3489
+ v[2] *= slowed / horizontal;
3490
+ }
3491
+ const moving = horizontal > 0.05 || knock.airborne;
3492
+ if (knock.down) {
3493
+ if (moving) knock.lying = Math.min(1, knock.lying + dt / FALL_SECONDS);
3494
+ else {
3495
+ knock.lying = 1;
3496
+ if (alive) {
3497
+ knock.lieLeft = knock.lieLeft > 0 ? knock.lieLeft : LIE_SECONDS + Math.hypot(v[0], v[2]) * STAND_UP_PER_SPEED;
3498
+ knock.lieLeft -= dt;
3499
+ if (knock.lieLeft <= 0) knock.down = false;
3500
+ }
3501
+ }
3502
+ } else if (knock.lying > 0) knock.lying = Math.max(0, knock.lying - dt / RISE_SECONDS);
3503
+ return !moving && !knock.down && knock.lying === 0;
3504
+ }
3505
+
3439
3506
  // src/test-area/scenarios.ts
3440
3507
  import { SEGMENT_IDS } from "@adatechnology/game-characters";
3441
3508
  var LIMB_SEGMENTS = ["upperArmL", "forearmL", "handL", "upperArmR", "forearmR", "handR", "thighL", "shinL", "footL", "thighR", "shinR", "footR"];
@@ -3840,6 +3907,17 @@ function createCharactersTestModule(options = {}) {
3840
3907
  };
3841
3908
  const animate = (entry, dt) => {
3842
3909
  const { root, character } = entry;
3910
+ if (entry.knock) {
3911
+ const rig = root.userData.rig;
3912
+ const ground = (x, z) => host?.groundAt(x, z) ?? 0;
3913
+ const done = stepKnock(entry.knock, root.position, dt, ground, character.alive);
3914
+ if (done) entry.knock = void 0;
3915
+ if (character.alive && rig) rig.fig.rotation.x = -(entry.knock?.lying ?? 0) * KNOCKED_DOWN_ANGLE;
3916
+ if (entry.knock && entry.knock.lying > 0 && character.alive) {
3917
+ entry.eyes?.off();
3918
+ return;
3919
+ }
3920
+ }
3843
3921
  if (!character.alive) {
3844
3922
  entry.tilt = Math.min(1, entry.tilt + dt * 2);
3845
3923
  const rig = root.userData.rig;
@@ -4045,6 +4123,15 @@ function createCharactersTestModule(options = {}) {
4045
4123
  wounds = createWounds(lent.THREE, { random, scene: scene2, groundAt: (x, z) => lent.groundAt(x, z), ...bridge ? { bridge } : {}, ...sound ? { sound } : {} });
4046
4124
  for (const entry of entries.values()) trackEntry(entry);
4047
4125
  const stopChange = lent.bench?.onChange(onChange);
4126
+ const stopEvents = lent.on((event) => {
4127
+ if (event.type !== "explosion") return;
4128
+ for (const entry of entries.values()) {
4129
+ if (!entry.root.parent) continue;
4130
+ const p2 = entry.root.position;
4131
+ const knock = knockOf(event, [p2.x, p2.y, p2.z], bodyMass(entry.character.spec.height, entry.character.spec.build), lent.blastShove);
4132
+ if (knock) entry.knock = entry.knock ? { ...knock, lying: entry.knock.lying } : knock;
4133
+ }
4134
+ });
4048
4135
  const stopFrame = lent.onFrame((dt) => {
4049
4136
  time += dt;
4050
4137
  for (const [id, entry] of entries) {
@@ -4060,6 +4147,7 @@ function createCharactersTestModule(options = {}) {
4060
4147
  });
4061
4148
  return () => {
4062
4149
  stopChange?.();
4150
+ stopEvents();
4063
4151
  stopFrame();
4064
4152
  clearOverlays();
4065
4153
  wounds?.dispose();
@@ -4095,7 +4183,1023 @@ var BODY_MATERIAL_DEF = {
4095
4183
  var registerBodyMaterial = (world) => {
4096
4184
  if (!world.materials.has(BODY_MATERIAL_DEF.id)) world.materials.set(BODY_MATERIAL_DEF.id, BODY_MATERIAL_DEF);
4097
4185
  };
4186
+
4187
+ // src/player/avatar.ts
4188
+ import {
4189
+ POSE_CLIPS,
4190
+ clipEventsBetween,
4191
+ createPoseClipRegistry,
4192
+ createRng as createRng4,
4193
+ filterPoseChannels,
4194
+ samplePoseClip,
4195
+ walkCadence,
4196
+ walkGait
4197
+ } from "@adatechnology/game-characters";
4198
+
4199
+ // src/presets/player-avatar.json
4200
+ var player_avatar_default = {
4201
+ source: {
4202
+ file: "site/pages/jogo/zombiada-modo-livre.html",
4203
+ variable: "PB specs[0] ('voc\xEA') and PLAYER_COR",
4204
+ note: "The team game's player body: the Besti\xE1rio/Elenco human body. Overridable by `createPlayerAvatar(THREE, { spec, shirtColor })`."
4205
+ },
4206
+ spec: {
4207
+ type: "Lento",
4208
+ human: true,
4209
+ decay: 0,
4210
+ seed: 4242,
4211
+ skin: "b07a52",
4212
+ fem: false,
4213
+ life: 1,
4214
+ hs: "short",
4215
+ hair: "2a1e16"
4216
+ },
4217
+ shirtColor: "3d5a73"
4218
+ };
4219
+
4220
+ // src/player/avatar.ts
4221
+ var TWO_PI = Math.PI * 2;
4222
+ var MAX_GAIT = 1.4;
4223
+ var CLIP_BLEND = 0.12;
4224
+ var FALL_SECONDS2 = 0.3;
4225
+ var HIP_HEIGHT = 0.95;
4226
+ var WALK_ARM = 0.45;
4227
+ var WALK_BOB = 0.035;
4228
+ var WALK_LEAN = 0.04;
4229
+ var CROUCH = { thigh: -0.95, thighWalk: 0.3, shin: 1.9, lean: 0.25, drop: -0.35 };
4230
+ var AIMED = { shoulder: -1.42, perAim: 0.85, swing: 0.02, zRight: -0.54, zLeft: 0.36, elbowRight: -0.04, elbowLeft: -0.12 };
4231
+ 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 };
4232
+ var HAND_OFFSET = { x: 0, y: -0.33, z: 0.04 };
4233
+ var hexOf = (value) => typeof value === "string" ? Number.parseInt(value, 16) : value;
4234
+ var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
4235
+ var fract = (value) => value - Math.floor(value);
4236
+ var createPlayerAvatar = (THREE, options = {}) => {
4237
+ const spec = { ...player_avatar_default.spec, ...options.spec };
4238
+ for (const key of ["skin", "hair"]) if (typeof spec[key] === "string") spec[key] = hexOf(spec[key]);
4239
+ const best = createBestBuilder(THREE, { types: [{ name: String(spec.type), eye: 0 }], random: createRng4(Number(spec.seed ?? 1)) });
4240
+ const body = best.build(spec);
4241
+ best.topM().color.setHex(options.shirtColor ?? hexOf(player_avatar_default.shirtColor));
4242
+ const rig = body.userData.rig;
4243
+ const profile = { f: 1.1, a: 0.28, k: 0.35, arm: WALK_ARM, bob: WALK_BOB };
4244
+ const animator = createRigAnimator({ gaits: { [rig.type]: profile } });
4245
+ const clips = options.clips ?? createPoseClipRegistry(POSE_CLIPS);
4246
+ const blendDefault = options.blend ?? CLIP_BLEND;
4247
+ const fallSeconds = options.fallSeconds ?? FALL_SECONDS2;
4248
+ const smooth = options.cascade ?? true;
4249
+ const root = new THREE.Group();
4250
+ const tilt = new THREE.Group();
4251
+ tilt.add(body);
4252
+ root.add(tilt);
4253
+ const colorOf2 = (object) => object?.material?.color?.getHex();
4254
+ const meshesOf = (node) => node.children.filter((child) => child.isMesh);
4255
+ const shirt = options.shirtColor ?? hexOf(player_avatar_default.shirtColor);
4256
+ const shoe = meshesOf(rig.legs[0].kn).find((mesh) => mesh.scale.z > 1.5);
4257
+ const forearm = colorOf2(meshesOf(rig.arms[1].el)[0]);
4258
+ const colors = {
4259
+ skin: Number(spec.skin),
4260
+ shirt,
4261
+ trousers: colorOf2(meshesOf(rig.legs[0].g)[0]) ?? 2962496,
4262
+ shoes: colorOf2(shoe) ?? 1841690,
4263
+ longSleeves: forearm === shirt
4264
+ };
4265
+ const sockets = { R: new THREE.Object3D(), L: new THREE.Object3D() };
4266
+ const armOf = { R: rig.arms[1], L: rig.arms[0] };
4267
+ for (const side of ["R", "L"]) {
4268
+ sockets[side].name = `hand_${side}`;
4269
+ sockets[side].position.set(HAND_OFFSET.x, HAND_OFFSET.y, HAND_OFFSET.z);
4270
+ armOf[side].el.add(sockets[side]);
4271
+ }
4272
+ const legRestX = rig.legs.map((leg) => leg.g.position.x);
4273
+ const legSide = (index) => index === 0 ? 1 : -1;
4274
+ let cycles = 0;
4275
+ let time = 0;
4276
+ let fall = 0;
4277
+ let run = null;
4278
+ const listeners = /* @__PURE__ */ new Set();
4279
+ const emit = (event, onEvent) => {
4280
+ onEvent?.(event);
4281
+ for (const listener of listeners) listener(event);
4282
+ };
4283
+ const readPose = () => ({
4284
+ lean: rig.upper.rotation.x,
4285
+ twist: rig.upper.rotation.y,
4286
+ side: rig.upper.rotation.z,
4287
+ drop: rig.fig.position.y - rig.dropBase,
4288
+ headX: rig.headG.rotation.x,
4289
+ headY: rig.headG.rotation.y,
4290
+ headZ: rig.headG.rotation.z,
4291
+ armR: rig.arms[0].g.rotation.x,
4292
+ armL: rig.arms[1].g.rotation.x,
4293
+ elbR: rig.arms[0].el.rotation.x,
4294
+ elbL: rig.arms[1].el.rotation.x,
4295
+ armRz: rig.arms[0].g.rotation.z,
4296
+ armLz: rig.arms[1].g.rotation.z,
4297
+ legL: rig.legs[0].g.rotation.x,
4298
+ legR: rig.legs[1].g.rotation.x,
4299
+ kneeL: rig.legs[0].kn.rotation.x,
4300
+ kneeR: rig.legs[1].kn.rotation.x
4301
+ });
4302
+ const armed = (pitch, gait, hip) => {
4303
+ const aim = clamp((pitch + 1) / 3, 0, 0.99) * 3 - 1;
4304
+ const shoulder = AIMED.shoulder + aim * AIMED.perAim + AIMED.swing * Math.sin(cycles * TWO_PI) * gait;
4305
+ rig.legs.forEach((leg, index) => {
4306
+ const x = legRestX[index];
4307
+ leg.g.rotation.y = hip;
4308
+ leg.g.position.x = x * Math.cos(hip);
4309
+ leg.g.position.z = -x * Math.sin(hip);
4310
+ });
4311
+ return { armR: shoulder, armL: shoulder, armRz: AIMED.zRight, armLz: AIMED.zLeft, elbR: AIMED.elbowRight, elbL: AIMED.elbowLeft };
4312
+ };
4313
+ const resetLegYaw = () => {
4314
+ rig.legs.forEach((leg, index) => {
4315
+ leg.g.rotation.y = 0;
4316
+ leg.g.position.x = legRestX[index];
4317
+ leg.g.position.z = 0;
4318
+ });
4319
+ };
4320
+ const crouchPose = (gait) => {
4321
+ const swing = (leg) => {
4322
+ const legS = legSide(leg);
4323
+ return CROUCH.thigh - Math.sin(cycles * TWO_PI) * legS * CROUCH.thighWalk * gait;
4324
+ };
4325
+ return {
4326
+ legL: swing(0),
4327
+ legR: swing(1),
4328
+ kneeL: CROUCH.shin,
4329
+ kneeR: CROUCH.shin,
4330
+ lean: WALK_LEAN * gait + CROUCH.lean,
4331
+ drop: Math.abs(Math.sin(cycles * TWO_PI)) * WALK_BOB * gait + CROUCH.drop
4332
+ };
4333
+ };
4334
+ const downPose = () => {
4335
+ const ph = cycles * TWO_PI;
4336
+ const gait = 1;
4337
+ const leg = (index) => {
4338
+ const legS = legSide(index);
4339
+ const walkS = Math.sin(ph) * legS;
4340
+ const bend = fract(cycles * 3) * legS > 0.3 ? fract(cycles * 3) * DOWN.shinBend : Math.max(0, Math.cos(ph + (legS > 0 ? 0 : Math.PI))) * DOWN.shinSwing * gait;
4341
+ return { hip: -walkS * DOWN.thighWalk * gait, knee: bend };
4342
+ };
4343
+ const arm = (index) => {
4344
+ const side = index === 0 ? 1 : -1;
4345
+ const armS = Math.sin(ph) * (side > 0 ? 1 : -1);
4346
+ const shoulder = fract(cycles * 5) > 0.5 && side > 0 ? DOWN.armHigh : DOWN.armLow + DOWN.armWave * Math.sin(cycles * 5 + side);
4347
+ return { shoulder, z: side * (DOWN.armZ + DOWN.armZWave * Math.sin(cycles * 3)), elbow: -0.35 - Math.max(0, armS) * 0.4 * gait };
4348
+ };
4349
+ const [l, r] = [leg(0), leg(1)];
4350
+ const [a0, a1] = [arm(0), arm(1)];
4351
+ return {
4352
+ legL: l.hip,
4353
+ kneeL: l.knee,
4354
+ legR: r.hip,
4355
+ kneeR: r.knee,
4356
+ armR: a0.shoulder,
4357
+ armRz: a0.z,
4358
+ elbR: a0.elbow,
4359
+ armL: a1.shoulder,
4360
+ armLz: a1.z,
4361
+ elbL: a1.elbow,
4362
+ lean: WALK_LEAN * gait,
4363
+ drop: Math.abs(Math.sin(ph)) * WALK_BOB * gait
4364
+ };
4365
+ };
4366
+ const placeBody = (progress) => {
4367
+ const fk = progress * progress * (3 - 2 * progress);
4368
+ const flip = fract(cycles * 1.7) >= 0.5 ? 1 : 0;
4369
+ tilt.rotation.set((-DOWN.tilt + flip * 2 * DOWN.tilt) * fk, 0, Math.sin(cycles * 7) * DOWN.roll * fk, "XYZ");
4370
+ const hip = new THREE.Vector3(0, HIP_HEIGHT, 0);
4371
+ const target = new THREE.Vector3(0, DOWN.hipY, -DOWN.hipZ + flip * DOWN.hipZFlip);
4372
+ const place = new THREE.Vector3(0, HIP_HEIGHT * (1 - fk), 0).addScaledVector(target, fk);
4373
+ tilt.position.copy(place.sub(hip.applyEuler(tilt.rotation)));
4374
+ };
4375
+ const fireClipEvents = (r, from, to) => {
4376
+ for (const event of clipEventsBetween(r.clip, from, to)) emit({ ...event, clip: r.clip.id }, r.onEvent);
4377
+ };
4378
+ const stepClip = (dt, down) => {
4379
+ if (!run) return;
4380
+ const r = run;
4381
+ const from = r.time;
4382
+ r.time += dt;
4383
+ const { duration } = r.clip;
4384
+ if (r.clip.loop && r.time > duration) {
4385
+ fireClipEvents(r, from, duration);
4386
+ r.time -= duration * Math.floor(r.time / duration);
4387
+ fireClipEvents(r, -1e-9, r.time);
4388
+ } else {
4389
+ fireClipEvents(r, from, Math.min(r.time, duration));
4390
+ if (r.time >= duration && !r.clip.loop) r.target = 0;
4391
+ }
4392
+ r.weight = r.blend <= 0 ? r.target : clamp(r.weight + (r.target ? dt : -dt) / r.blend, 0, 1);
4393
+ if (r.target === 0 && r.weight <= 0) {
4394
+ run = null;
4395
+ return;
4396
+ }
4397
+ if (down) return;
4398
+ const sampled = filterPoseChannels(samplePoseClip(r.clip, Math.min(r.time, duration)), r.clip.channels ?? "full");
4399
+ const current = readPose();
4400
+ const blended = {};
4401
+ for (const [name, value] of Object.entries(sampled)) if (name in current) blended[name] = current[name] + (value - current[name]) * r.weight;
4402
+ animator.setPose(rig, blended);
4403
+ };
4404
+ return {
4405
+ root,
4406
+ rig,
4407
+ colors,
4408
+ three: THREE,
4409
+ get playing() {
4410
+ return run ? run.clip.id : null;
4411
+ },
4412
+ update(dt, state = {}) {
4413
+ const speed = state.speed ?? 0;
4414
+ const moving = speed >= 0.05;
4415
+ if (moving) cycles += dt * walkCadence(speed);
4416
+ const gait = Math.min(MAX_GAIT, walkGait(speed));
4417
+ profile.arm = WALK_ARM * gait;
4418
+ profile.bob = WALK_BOB * gait;
4419
+ time = cycles / walkCadence(Math.max(speed, 0));
4420
+ animator.animate(body, time, { speed });
4421
+ const down = !!state.down;
4422
+ fall = fallSeconds <= 0 ? down ? 1 : 0 : clamp(fall + (down ? dt : -dt) / fallSeconds, 0, 1);
4423
+ resetLegYaw();
4424
+ if (down) animator.setPose(rig, downPose());
4425
+ else {
4426
+ if (state.crouch) animator.setPose(rig, crouchPose(gait));
4427
+ if (state.armed) animator.setPose(rig, armed(state.pitch ?? 0, gait, state.hip ?? 0));
4428
+ }
4429
+ placeBody(fall);
4430
+ stepClip(dt, down);
4431
+ if (smooth) animator.cascade(rig, dt);
4432
+ },
4433
+ play(clip, playOptions = {}) {
4434
+ const found = typeof clip === "string" ? clips.get(clip) : clip;
4435
+ if (!found) throw new Error(`Unknown pose clip: ${String(clip)}`);
4436
+ run = { clip: found, time: 0, weight: run?.weight ?? 0, target: 1, blend: playOptions.blend ?? blendDefault, ...playOptions.onEvent ? { onEvent: playOptions.onEvent } : {} };
4437
+ fireClipEvents(run, -1e-9, 0);
4438
+ run.time = 0;
4439
+ },
4440
+ stop() {
4441
+ if (run) run.target = 0;
4442
+ },
4443
+ hand: (side) => sockets[side],
4444
+ on(_type, listener) {
4445
+ listeners.add(listener);
4446
+ return () => void listeners.delete(listener);
4447
+ },
4448
+ dispose() {
4449
+ run = null;
4450
+ listeners.clear();
4451
+ body.traverse((object) => {
4452
+ const mesh = object;
4453
+ mesh.geometry?.dispose();
4454
+ const material = mesh.material;
4455
+ if (Array.isArray(material)) material.forEach((m) => m.dispose());
4456
+ else material?.dispose();
4457
+ });
4458
+ root.removeFromParent();
4459
+ }
4460
+ };
4461
+ };
4462
+
4463
+ // src/player/room-avatar.ts
4464
+ import { ATTACK_CLIPS, FIRST_PERSON_CLIPS } from "@adatechnology/game-characters-people";
4465
+ import { mirrorPoseClip } from "@adatechnology/game-characters";
4466
+
4467
+ // src/first-person/arms.ts
4468
+ import { clipEventsBetween as clipEventsBetween2, sampleFirstPersonClip } from "@adatechnology/game-characters";
4469
+
4470
+ // src/presets/first-person-arms.json
4471
+ var first_person_arms_default = {
4472
+ source: {
4473
+ note: "Authored in the kit: no team delivered first-person arms. The rig is in metres in the camera's space (x right, y up, the view looks down -z); rest poses use the hand frame of HandPose in game-contracts, Euler [roll, yaw, pitch] in the order YZX (yaw pi/2 points the fingers forward)."
4474
+ },
4475
+ rig: {
4476
+ shoulder: [0.16, -0.36, 0.18],
4477
+ upper: 0.32,
4478
+ fore: 0.3,
4479
+ reachShift: 0.35,
4480
+ wrist: 0.07,
4481
+ elbowPole: [0.45, -1, 0.2],
4482
+ shoulderLeft: [-0.07, -0.46, 0.18],
4483
+ elbowPoleLeft: [-0.1, -1, 0.2],
4484
+ radius: { shoulder: 0.04, elbow: 0.034, wrist: 0.028, cuff: 0.031 },
4485
+ hip: [0.12, -0.82, 0.04],
4486
+ thigh: 0.46,
4487
+ shin: 0.46,
4488
+ kneePole: [0.1, 0.5, -1],
4489
+ foot: { p: [0.13, -0.85, -0.05], r: [0, 1.5708, 0] }
4490
+ },
4491
+ stances: {
4492
+ relaxed: {
4493
+ R: { p: [0.2, -0.2, -0.36], r: [0.2, 1.45, -0.4], grip: 0.35 },
4494
+ L: "mirror"
4495
+ },
4496
+ fists: {
4497
+ R: { p: [0.15, -0.12, -0.4], r: [0, 1.5, 0.15], grip: 1 },
4498
+ L: "mirror"
4499
+ },
4500
+ hold: { follow: true, fallback: "relaxed" }
4501
+ },
4502
+ bob: { frequency: 2.3, x: 6e-3, y: 8e-3, run: 1.5, fullSpeed: 4, crouch: 0.02 }
4503
+ };
4504
+
4505
+ // src/first-person/hand.ts
4506
+ var FINGERS = [
4507
+ { z: -0.03, lengths: [0.034, 0.024, 0.02], width: 0.016 },
4508
+ { z: -0.0105, lengths: [0.038, 0.027, 0.022], width: 0.0165 },
4509
+ { z: 9e-3, lengths: [0.035, 0.025, 0.02], width: 0.0155 },
4510
+ { z: 0.027, lengths: [0.029, 0.02, 0.018], width: 0.0135 }
4511
+ ];
4512
+ var PALM = { length: 0.09, thick: 0.026, width: 0.09, wristWidth: 0.066 };
4513
+ var FINGER_THICK = 0.0135;
4514
+ var CURL = { near: 1.05, mid: 1.25, far: 0.85, spread: 0.06 };
4515
+ var THUMB = { base: [5e-3, -6e-3, -0.044], near: 0.034, far: 0.028, width: 0.019 };
4516
+ var createHandMesh = (THREE, skin, wristSkin = skin) => {
4517
+ const root = new THREE.Group();
4518
+ root.name = "first-person-hand";
4519
+ const geometries = [];
4520
+ const box = (parent, sx, sy, sz, x, y, z, material = skin) => {
4521
+ const geometry = new THREE.BoxGeometry(sx, sy, sz);
4522
+ geometries.push(geometry);
4523
+ const mesh = new THREE.Mesh(geometry, material);
4524
+ mesh.position.set(x, y, z);
4525
+ mesh.frustumCulled = false;
4526
+ parent.add(mesh);
4527
+ return mesh;
4528
+ };
4529
+ const palmGeometry = new THREE.BoxGeometry(PALM.length, PALM.thick, PALM.width);
4530
+ const palmPosition = palmGeometry.getAttribute("position");
4531
+ for (let i = 0; i < palmPosition.count; i++) {
4532
+ if (palmPosition.getX(i) < 0) palmPosition.setZ(i, palmPosition.getZ(i) * PALM.wristWidth / PALM.width);
4533
+ }
4534
+ palmGeometry.computeVertexNormals();
4535
+ geometries.push(palmGeometry);
4536
+ const palm = new THREE.Mesh(palmGeometry, skin);
4537
+ palm.frustumCulled = false;
4538
+ root.add(palm);
4539
+ const wristGeometry = new THREE.CylinderGeometry(0.03, 0.033, 0.05, 8);
4540
+ wristGeometry.rotateZ(Math.PI / 2);
4541
+ geometries.push(wristGeometry);
4542
+ const wrist = new THREE.Mesh(wristGeometry, wristSkin);
4543
+ wrist.position.set(-PALM.length / 2 - 0.018, 0, 0);
4544
+ wrist.frustumCulled = false;
4545
+ root.add(wrist);
4546
+ const joints = [];
4547
+ FINGERS.forEach((finger, index) => {
4548
+ const segments = [];
4549
+ let parent = root;
4550
+ let at = PALM.length / 2;
4551
+ finger.lengths.forEach((length, k) => {
4552
+ const group = new THREE.Group();
4553
+ group.position.set(at, k === 0 ? 2e-3 : 0, k === 0 ? finger.z : 0);
4554
+ const taper = 1 - k * 0.07;
4555
+ box(group, length, FINGER_THICK * taper, finger.width * taper, length / 2, 0, 0);
4556
+ parent.add(group);
4557
+ segments.push(group);
4558
+ parent = group;
4559
+ at = length;
4560
+ });
4561
+ joints.push({ segments, weight: 1 + index * (CURL.spread / CURL.near) });
4562
+ });
4563
+ const thumbNear = new THREE.Group();
4564
+ thumbNear.rotation.order = "YZX";
4565
+ thumbNear.position.set(...THUMB.base);
4566
+ box(thumbNear, THUMB.near, FINGER_THICK * 1.1, THUMB.width, THUMB.near / 2, 0, 0);
4567
+ const thumbFar = new THREE.Group();
4568
+ thumbFar.position.set(THUMB.near, 0, 0);
4569
+ box(thumbFar, THUMB.far, FINGER_THICK, THUMB.width * 0.9, THUMB.far / 2, 0, 0);
4570
+ thumbNear.add(thumbFar);
4571
+ root.add(thumbNear);
4572
+ let lastGrip = 0;
4573
+ let indexCurl;
4574
+ const curl = [CURL.near, CURL.mid, CURL.far];
4575
+ const applyIndex = () => {
4576
+ const finger = joints[0];
4577
+ if (!finger) return;
4578
+ const k = indexCurl === void 0 ? lastGrip : Math.min(1, Math.max(0, indexCurl));
4579
+ finger.segments.forEach((segment, i) => segment.rotation.z = -curl[i] * k * finger.weight);
4580
+ };
4581
+ const setGrip = (grip) => {
4582
+ const k = Math.min(1, Math.max(0, grip));
4583
+ lastGrip = k;
4584
+ for (const joint of joints) joint.segments.forEach((segment, i) => segment.rotation.z = -curl[i] * k * joint.weight);
4585
+ thumbNear.rotation.set(0, 0.55 - 1.45 * k, -0.1 - 0.75 * k);
4586
+ thumbFar.rotation.z = -0.1 - 0.7 * k;
4587
+ applyIndex();
4588
+ };
4589
+ const setIndex = (value) => {
4590
+ indexCurl = value;
4591
+ applyIndex();
4592
+ };
4593
+ setGrip(0);
4594
+ return {
4595
+ root,
4596
+ setGrip,
4597
+ setIndex,
4598
+ dispose() {
4599
+ for (const geometry of geometries) geometry.dispose();
4600
+ }
4601
+ };
4602
+ };
4603
+
4604
+ // src/first-person/ik.ts
4605
+ var solveTwoBone = (root, target, upper, lower, pole, out) => {
4606
+ const line = out.line.copy(target).sub(root);
4607
+ let distance2 = line.length();
4608
+ if (distance2 < 1e-6) line.set(0, 0, -1);
4609
+ else line.divideScalar(distance2);
4610
+ const min = Math.abs(upper - lower) + 1e-4;
4611
+ const max = upper + lower - 1e-4;
4612
+ out.clamped = distance2 > max || distance2 < min;
4613
+ distance2 = Math.min(max, Math.max(min, distance2));
4614
+ const along = (distance2 * distance2 + upper * upper - lower * lower) / (2 * distance2);
4615
+ const across = Math.sqrt(Math.max(0, upper * upper - along * along));
4616
+ const side = out.side.copy(pole);
4617
+ side.addScaledVector(line, -side.dot(line));
4618
+ if (side.lengthSq() < 1e-8) side.set(0, 1, 0).addScaledVector(line, -line.y);
4619
+ if (side.lengthSq() < 1e-8) side.set(1, 0, 0);
4620
+ side.normalize();
4621
+ out.joint.copy(root).addScaledVector(line, along).addScaledVector(side, across);
4622
+ out.end.copy(root).addScaledVector(line, distance2);
4623
+ return out;
4624
+ };
4625
+ var createTwoBone = (THREE) => ({ joint: new THREE.Vector3(), end: new THREE.Vector3(), clamped: false, line: new THREE.Vector3(), side: new THREE.Vector3() });
4626
+
4627
+ // src/first-person/arms.ts
4628
+ var DEFAULT_BLEND = 0.18;
4629
+ var ELBOW_SEGMENTS = 8;
4630
+ var REACH_SLACK = 0.97;
4631
+ var CONTACT_PROBE = 1.4;
4632
+ var CONTACT_MIN = 0.12;
4633
+ var CONTACT_MARGIN = 5e-3;
4634
+ var CONTACT_PALM = 0.03;
4635
+ var FOOT_LENGTH = 0.24;
4636
+ var FOOT_SHOWN = 1e-3;
4637
+ var FILL = 0.3;
4638
+ var FIRST_PERSON_STANCES = first_person_arms_default.stances;
4639
+ var FIRST_PERSON_RIG = first_person_arms_default.rig;
4640
+ var FIRST_PERSON_BOB = first_person_arms_default.bob;
4641
+ var hexOf2 = (value) => typeof value === "string" ? Number.parseInt(value, 16) : value;
4642
+ var scaleHex = (hex2, factor) => {
4643
+ const channel = (shift) => Math.round((hex2 >> shift & 255) * factor);
4644
+ return channel(16) << 16 | channel(8) << 8 | channel(0);
4645
+ };
4646
+ var mix = (a, b, k) => a + (b - a) * k;
4647
+ var ease = (k) => k * k * (3 - 2 * k);
4648
+ var FIRST_PERSON_COLORS = {
4649
+ skin: hexOf2(player_avatar_default.spec.skin),
4650
+ sleeve: hexOf2(player_avatar_default.shirtColor),
4651
+ cuff: scaleHex(hexOf2(player_avatar_default.shirtColor), 0.72),
4652
+ trouser: 2962496,
4653
+ shoe: 1841690
4654
+ };
4655
+ var newPose = (THREE) => ({ p: new THREE.Vector3(), q: new THREE.Quaternion(), grip: 0, scale: 1 });
4656
+ var copyPose = (to, from) => {
4657
+ to.p.copy(from.p);
4658
+ to.q.copy(from.q);
4659
+ to.grip = from.grip;
4660
+ to.scale = from.scale;
4661
+ return to;
4662
+ };
4663
+ var mixPose = (THREE, out, a, b, k) => {
4664
+ out.p.lerpVectors(a.p, b.p, k);
4665
+ out.q.copy(a.q).slerp(b.q, k);
4666
+ out.grip = mix(a.grip, b.grip, k);
4667
+ out.scale = mix(a.scale, b.scale, k);
4668
+ return out;
4669
+ };
4670
+ var mirrorRotation = (THREE, q) => {
4671
+ const m = new THREE.Matrix4().makeRotationFromQuaternion(q);
4672
+ const e = m.elements;
4673
+ for (const [i, j] of [[0, 1], [0, 2], [1, 0], [2, 0]]) e[j * 4 + i] = -e[j * 4 + i];
4674
+ return new THREE.Quaternion().setFromRotationMatrix(m);
4675
+ };
4676
+ var mirrorQuaternion = (THREE, q) => {
4677
+ const m = new THREE.Matrix4().makeRotationFromQuaternion(q);
4678
+ const e = m.elements;
4679
+ for (let column = 0; column < 3; column++) e[column * 4] = -e[column * 4];
4680
+ for (let row = 0; row < 3; row++) e[8 + row] = -e[8 + row];
4681
+ return new THREE.Quaternion().setFromRotationMatrix(m);
4682
+ };
4683
+ function createFirstPersonArms(THREE, options = {}) {
4684
+ const colors = { ...FIRST_PERSON_COLORS, ...options.colors };
4685
+ const rig = {
4686
+ ...FIRST_PERSON_RIG,
4687
+ ...options.rig,
4688
+ radius: { ...FIRST_PERSON_RIG.radius, ...options.rig?.radius },
4689
+ foot: options.rig?.foot ?? FIRST_PERSON_RIG.foot
4690
+ };
4691
+ const stances = { ...FIRST_PERSON_STANCES, ...options.stances };
4692
+ const bob = { ...FIRST_PERSON_BOB, ...options.bob };
4693
+ const blendSeconds = options.blend ?? DEFAULT_BLEND;
4694
+ const clips = new Map((options.clips ?? []).map((clip) => [clip.id, clip]));
4695
+ const root = new THREE.Group();
4696
+ root.name = "first-person-arms";
4697
+ root.userData.viewmodel = true;
4698
+ const lit = (color) => new THREE.MeshLambertMaterial({ color, emissive: scaleHex(color, FILL) });
4699
+ const materials = {
4700
+ skin: lit(colors.skin),
4701
+ sleeve: lit(colors.sleeve),
4702
+ cuff: lit(colors.cuff),
4703
+ trouser: lit(colors.trouser),
4704
+ shoe: lit(colors.shoe),
4705
+ sole: lit(scaleHex(colors.shoe, 0.55))
4706
+ };
4707
+ const geometries = [];
4708
+ const Y = new THREE.Vector3(0, 1, 0);
4709
+ const limb = (parent, wide, narrow, length, material) => {
4710
+ const geometry = new THREE.CylinderGeometry(narrow, wide, length, 8, 1);
4711
+ geometry.translate(0, length / 2, 0);
4712
+ geometries.push(geometry);
4713
+ const mesh = new THREE.Mesh(geometry, material);
4714
+ mesh.frustumCulled = false;
4715
+ mesh.userData.length = length;
4716
+ parent.add(mesh);
4717
+ return mesh;
4718
+ };
4719
+ const ball = (parent, radius, material) => {
4720
+ const geometry = new THREE.SphereGeometry(radius, ELBOW_SEGMENTS, 6);
4721
+ geometries.push(geometry);
4722
+ const mesh = new THREE.Mesh(geometry, material);
4723
+ mesh.frustumCulled = false;
4724
+ parent.add(mesh);
4725
+ return mesh;
4726
+ };
4727
+ const place = (segment, from, to) => {
4728
+ const direction = new THREE.Vector3().subVectors(to, from);
4729
+ const length = direction.length();
4730
+ segment.position.copy(from);
4731
+ if (length > 1e-6) segment.quaternion.setFromUnitVectors(Y, direction.divideScalar(length));
4732
+ segment.scale.y = length / segment.userData.length;
4733
+ };
4734
+ const arms = {};
4735
+ for (const side of ["R", "L"]) {
4736
+ const sign = side === "R" ? 1 : -1;
4737
+ const hand = createHandMesh(THREE, materials.skin);
4738
+ const anchor = new THREE.Group();
4739
+ anchor.name = `first-person-hand-${side}`;
4740
+ const mirror = new THREE.Group();
4741
+ mirror.scale.z = sign;
4742
+ mirror.add(hand.root);
4743
+ anchor.add(mirror);
4744
+ root.add(anchor);
4745
+ const upper = limb(root, rig.radius.shoulder, rig.radius.elbow, rig.upper, materials.sleeve);
4746
+ const fore = limb(root, rig.radius.elbow, rig.radius.wrist, rig.fore, materials.sleeve);
4747
+ const cuff = limb(root, rig.radius.cuff, rig.radius.cuff, 0.03, materials.cuff);
4748
+ const elbow = ball(root, rig.radius.elbow * 1.02, materials.sleeve);
4749
+ elbow.name = `first-person-elbow-${side}`;
4750
+ arms[side] = { side, sign, hand, anchor, upper, fore, cuff, elbow, shown: newPose(THREE), from: newPose(THREE), mode: null, blend: 1, drawn: newPose(THREE), joints: { shoulder: new THREE.Vector3(), elbow: new THREE.Vector3(), wrist: new THREE.Vector3() } };
4751
+ }
4752
+ const leg = new THREE.Group();
4753
+ leg.name = "first-person-leg";
4754
+ leg.visible = false;
4755
+ root.add(leg);
4756
+ const thigh = limb(leg, 0.088, 0.068, rig.thigh, materials.trouser);
4757
+ const shin = limb(leg, 0.068, 0.05, rig.shin, materials.trouser);
4758
+ const knee = ball(leg, 0.07, materials.trouser);
4759
+ const shoe = new THREE.Group();
4760
+ const shoeBox = (sx, sy, sz, x, y, material) => {
4761
+ const geometry = new THREE.BoxGeometry(sx, sy, sz);
4762
+ geometries.push(geometry);
4763
+ const mesh = new THREE.Mesh(geometry, material);
4764
+ mesh.position.set(x, y, 0);
4765
+ mesh.frustumCulled = false;
4766
+ shoe.add(mesh);
4767
+ };
4768
+ shoeBox(0.27, 0.07, 0.1, 0.065, -0.05, materials.shoe);
4769
+ shoeBox(0.27, 0.025, 0.104, 0.065, -0.095, materials.sole);
4770
+ shoeBox(0.1, 0.05, 0.088, 0.17, -5e-3, materials.shoe);
4771
+ leg.add(shoe);
4772
+ const shoulderOf = (sign, out) => sign === -1 && rig.shoulderLeft ? out.set(...rig.shoulderLeft) : out.set(rig.shoulder[0] * sign, rig.shoulder[1], rig.shoulder[2]);
4773
+ 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]);
4774
+ const kneePole = new THREE.Vector3(...rig.kneePole);
4775
+ const hip = new THREE.Vector3(...rig.hip);
4776
+ const euler = new THREE.Euler(0, 0, 0, "YZX");
4777
+ const quaternionOf = (r) => new THREE.Quaternion().setFromEuler(euler.set(r[0], r[1], r[2], "YZX"));
4778
+ const restPose = (rest, mirror, out) => {
4779
+ out.p.set(...rest.p);
4780
+ out.q.copy(quaternionOf(rest.r));
4781
+ if (mirror) {
4782
+ out.p.x = -out.p.x;
4783
+ out.q.copy(mirrorQuaternion(THREE, out.q));
4784
+ }
4785
+ out.grip = rest.grip ?? 0;
4786
+ out.scale = 1;
4787
+ return out;
4788
+ };
4789
+ const DEFAULT_REST = { p: [0.17, -0.3, -0.3], r: [0, 1.5, 0], grip: 0.3 };
4790
+ const stanceRest = (id, side, out) => {
4791
+ const stance = stances[id] ?? stances.relaxed;
4792
+ const right = stance?.R ?? DEFAULT_REST;
4793
+ const left = stance?.L;
4794
+ if (side === "R") return restPose(right, false, out);
4795
+ return left && left !== "mirror" ? restPose(left, false, out) : restPose(right, true, out);
4796
+ };
4797
+ const camPosition = new THREE.Vector3();
4798
+ const camQuaternion = new THREE.Quaternion();
4799
+ const camInverse = new THREE.Quaternion();
4800
+ const clamped = newPose(THREE);
4801
+ const probeAt = new THREE.Vector3();
4802
+ const probeForward = new THREE.Vector3();
4803
+ const probeFingers = new THREE.Vector3();
4804
+ let contact;
4805
+ const keepOutside = (pose, reach) => {
4806
+ if (!contact) return pose;
4807
+ probeAt.set(pose.p.x, pose.p.y, 0).applyQuaternion(camQuaternion).add(camPosition);
4808
+ probeForward.set(0, 0, -1).applyQuaternion(camQuaternion);
4809
+ const distance2 = contact([probeAt.x, probeAt.y, probeAt.z], [probeForward.x, probeForward.y, probeForward.z], CONTACT_PROBE);
4810
+ if (distance2 === null) return pose;
4811
+ const limit = Math.max(CONTACT_MIN, distance2 - reach - CONTACT_MARGIN);
4812
+ if (-pose.p.z <= limit) return pose;
4813
+ copyPose(clamped, pose);
4814
+ clamped.p.z = -limit;
4815
+ return clamped;
4816
+ };
4817
+ const forward = (pose, length) => Math.max(CONTACT_PALM, -probeFingers.set(1, 0, 0).applyQuaternion(pose.q).z * length * pose.scale);
4818
+ const handReach = (pose) => forward(pose, 0.085);
4819
+ const camScale = new THREE.Vector3();
4820
+ const toCamera = (target, grip, scale, out) => {
4821
+ out.p.set(...target.position).sub(camPosition).applyQuaternion(camInverse);
4822
+ out.q.set(...target.quaternion).premultiply(camInverse);
4823
+ out.grip = grip;
4824
+ out.scale = scale;
4825
+ return out;
4826
+ };
4827
+ const listeners = /* @__PURE__ */ new Set();
4828
+ let run = null;
4829
+ const emit = (event) => {
4830
+ for (const listener of listeners) listener(event);
4831
+ };
4832
+ const fire = (clip, from, to) => {
4833
+ for (const event of clipEventsBetween2(clip, from, to)) emit({ ...event, clip: clip.id });
4834
+ };
4835
+ let stanceId = "relaxed";
4836
+ let phase = 0;
4837
+ let swing = 0;
4838
+ let sink = 0;
4839
+ let first = true;
4840
+ const bobOffset = new THREE.Vector3();
4841
+ const desired = newPose(THREE);
4842
+ const channelPose = newPose(THREE);
4843
+ const mixed = newPose(THREE);
4844
+ let indexCurl;
4845
+ const bone = createTwoBone(THREE);
4846
+ const shoulder = new THREE.Vector3();
4847
+ const wristAt = new THREE.Vector3();
4848
+ const palmAt = new THREE.Vector3();
4849
+ const fingers = new THREE.Vector3();
4850
+ const towards = new THREE.Vector3();
4851
+ const fromChannel = (channel, side, base, out) => {
4852
+ out.p.set(...channel.p);
4853
+ out.q.copy(quaternionOf(channel.r));
4854
+ if (channel.mirror && side === "L") {
4855
+ out.p.x = -out.p.x;
4856
+ out.q.copy(mirrorQuaternion(THREE, out.q));
4857
+ }
4858
+ out.grip = channel.grip ?? base.grip;
4859
+ out.scale = base.scale;
4860
+ return out;
4861
+ };
4862
+ const drawArm = (arm, pose) => {
4863
+ copyPose(arm.drawn, pose);
4864
+ const scale = pose.scale;
4865
+ fingers.set(1, 0, 0).applyQuaternion(pose.q);
4866
+ wristAt.copy(pose.p).addScaledVector(fingers, -rig.wrist * scale);
4867
+ shoulderOf(arm.sign, shoulder).add(bobOffset);
4868
+ const excess = shoulder.distanceTo(wristAt) - (rig.upper + rig.fore) * REACH_SLACK;
4869
+ if (excess > 0 && rig.reachShift > 0) shoulder.addScaledVector(towards.subVectors(wristAt, shoulder).normalize(), Math.min(excess, rig.reachShift));
4870
+ solveTwoBone(shoulder, wristAt, rig.upper, rig.fore, poleOf(arm.sign), bone);
4871
+ palmAt.copy(bone.end).addScaledVector(fingers, rig.wrist * scale);
4872
+ arm.drawn.p.copy(palmAt);
4873
+ arm.anchor.position.copy(palmAt);
4874
+ arm.anchor.quaternion.copy(pose.q);
4875
+ arm.anchor.scale.setScalar(scale);
4876
+ arm.hand.setGrip(pose.grip);
4877
+ arm.hand.setIndex(arm.side === "R" ? indexCurl : void 0);
4878
+ arm.joints.shoulder.copy(shoulder);
4879
+ arm.joints.elbow.copy(bone.joint);
4880
+ arm.joints.wrist.copy(bone.end);
4881
+ place(arm.upper, shoulder, bone.joint);
4882
+ place(arm.fore, bone.joint, bone.end);
4883
+ arm.elbow.position.copy(bone.joint);
4884
+ const toWrist = new THREE.Vector3().subVectors(bone.end, bone.joint).normalize();
4885
+ place(arm.cuff, new THREE.Vector3().copy(bone.end).addScaledVector(toWrist, -0.02), new THREE.Vector3().copy(bone.end).addScaledVector(toWrist, 0.012));
4886
+ };
4887
+ const footPose = newPose(THREE);
4888
+ const footRest = newPose(THREE);
4889
+ const ankle = new THREE.Vector3();
4890
+ const hipMirrored = new THREE.Vector3(-hip.x, hip.y, hip.z);
4891
+ const kneePoleMirrored = new THREE.Vector3(-kneePole.x, kneePole.y, kneePole.z);
4892
+ const drawLeg = (pose, left) => {
4893
+ const origin = left ? hipMirrored : hip;
4894
+ solveTwoBone(origin, pose.p, rig.thigh, rig.shin, left ? kneePoleMirrored : kneePole, bone);
4895
+ ankle.copy(bone.end);
4896
+ place(thigh, origin, bone.joint);
4897
+ place(shin, bone.joint, ankle);
4898
+ knee.position.copy(bone.joint);
4899
+ shoe.position.copy(ankle);
4900
+ shoe.quaternion.copy(pose.q);
4901
+ };
4902
+ const modeOf = (key, arm) => {
4903
+ if (arm.mode === key) return;
4904
+ copyPose(arm.from, arm.shown);
4905
+ arm.mode = key;
4906
+ arm.blend = first ? 1 : 0;
4907
+ };
4908
+ return {
4909
+ root,
4910
+ get playing() {
4911
+ return run ? run.clip.id : null;
4912
+ },
4913
+ get stance() {
4914
+ return stanceId;
4915
+ },
4916
+ play(id, options2) {
4917
+ const clip = clips.get(id);
4918
+ if (!clip) return false;
4919
+ run = { clip, time: 0, left: options2?.leg === "left" };
4920
+ fire(clip, -1e-9, 0);
4921
+ return true;
4922
+ },
4923
+ stop() {
4924
+ run = null;
4925
+ },
4926
+ update(dt, state) {
4927
+ const { camera } = state;
4928
+ contact = state.contact;
4929
+ camera.updateWorldMatrix(true, false);
4930
+ camera.matrixWorld.decompose(camPosition, camQuaternion, camScale);
4931
+ camInverse.copy(camQuaternion).invert();
4932
+ root.position.copy(camPosition);
4933
+ root.quaternion.copy(camQuaternion);
4934
+ root.updateMatrixWorld(true);
4935
+ const speed = state.speed ?? 0;
4936
+ const target = Math.min(1, speed / bob.fullSpeed) * (state.running ? bob.run : 1);
4937
+ swing += (target - swing) * Math.min(1, dt * 8);
4938
+ phase += dt * speed * bob.frequency;
4939
+ sink += ((state.crouch ? 1 : 0) - sink) * Math.min(1, dt * 8);
4940
+ bobOffset.set(Math.sin(phase) * bob.x * swing, Math.sin(phase * 2) * bob.y * swing - bob.crouch * sink, 0);
4941
+ stanceId = state.stance ?? "relaxed";
4942
+ const stance = stances[stanceId] ?? stances.relaxed;
4943
+ const targets = state.targets ?? null;
4944
+ if (run) {
4945
+ const from = run.time;
4946
+ run.time += dt;
4947
+ fire(run.clip, from, Math.min(run.time, run.clip.duration));
4948
+ }
4949
+ const sample = run ? sampleFirstPersonClip(run.clip, Math.min(run.time, run.clip.duration)) : null;
4950
+ for (const side of ["R", "L"]) {
4951
+ const arm = arms[side];
4952
+ const target2 = targets?.[side];
4953
+ let mode;
4954
+ if (stance?.follow && target2) {
4955
+ mode = "target";
4956
+ toCamera(target2, (side === "L" ? targets?.gripL : void 0) ?? targets?.grip ?? 1, targets?.scale ?? 1, desired);
4957
+ } else {
4958
+ mode = `rest:${stance?.follow ? stance.fallback ?? "relaxed" : stanceId}`;
4959
+ stanceRest(stance?.follow ? stance.fallback ?? "relaxed" : stanceId, side, desired);
4960
+ desired.p.add(bobOffset);
4961
+ }
4962
+ modeOf(mode, arm);
4963
+ if (side === "R") indexCurl = mode === "target" ? targets?.fingers?.index : void 0;
4964
+ arm.blend = blendSeconds <= 0 ? 1 : Math.min(1, arm.blend + dt / blendSeconds);
4965
+ if (arm.blend >= 1) copyPose(arm.shown, desired);
4966
+ else mixPose(THREE, arm.shown, arm.from, desired, ease(arm.blend));
4967
+ const channel = sample?.[side];
4968
+ if (channel && channel.w > 0) {
4969
+ fromChannel(channel, side, arm.shown, channelPose);
4970
+ mixPose(THREE, mixed, arm.shown, channelPose, channel.w);
4971
+ drawArm(arm, mode === "target" ? mixed : keepOutside(mixed, handReach(mixed)));
4972
+ } else drawArm(arm, mode === "target" ? arm.shown : keepOutside(arm.shown, handReach(arm.shown)));
4973
+ }
4974
+ const foot = sample?.F;
4975
+ leg.visible = !!foot && foot.w > FOOT_SHOWN;
4976
+ if (foot && leg.visible) {
4977
+ restPose(rig.foot, false, footRest);
4978
+ fromChannel(foot, "R", footRest, footPose);
4979
+ mixPose(THREE, mixed, footRest, footPose, foot.w);
4980
+ copyPose(footPose, mixed);
4981
+ if (run?.left) {
4982
+ footPose.p.x = -footPose.p.x;
4983
+ footPose.q.copy(mirrorRotation(THREE, footPose.q));
4984
+ }
4985
+ drawLeg(contact ? keepOutside(footPose, forward(footPose, FOOT_LENGTH)) : footPose, run?.left ?? false);
4986
+ }
4987
+ if (run && run.time >= run.clip.duration) run = null;
4988
+ first = false;
4989
+ },
4990
+ on(_type, listener) {
4991
+ listeners.add(listener);
4992
+ return () => void listeners.delete(listener);
4993
+ },
4994
+ joints(side) {
4995
+ const { shoulder: shoulder2, elbow, wrist } = arms[side].joints;
4996
+ return { shoulder: shoulder2.clone(), elbow: elbow.clone(), wrist: wrist.clone() };
4997
+ },
4998
+ handPose(side) {
4999
+ const pose = arms[side].drawn;
5000
+ return { position: pose.p.clone(), quaternion: pose.q.clone(), grip: pose.grip };
5001
+ },
5002
+ dispose() {
5003
+ run = null;
5004
+ listeners.clear();
5005
+ for (const arm of Object.values(arms)) arm.hand.dispose();
5006
+ for (const geometry of geometries) geometry.dispose();
5007
+ for (const material of Object.values(materials)) material.dispose();
5008
+ root.removeFromParent();
5009
+ }
5010
+ };
5011
+ }
5012
+
5013
+ // src/player/room-avatar.ts
5014
+ var impactOf = (clip) => clip.events?.find((event) => event.type === "impact")?.t ?? null;
5015
+ var toRoomAvatar = (avatar, options = {}) => {
5016
+ const arms = options.firstPerson === false ? null : createFirstPersonArms(avatar.three, {
5017
+ 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 },
5018
+ clips: options.firstPersonClips ?? FIRST_PERSON_CLIPS,
5019
+ ...options.firstPerson
5020
+ });
5021
+ const strikes = new Map((options.strikeClips ?? ATTACK_CLIPS).map((clip) => [clip.id, clip]));
5022
+ const mirrored = /* @__PURE__ */ new Map();
5023
+ const leftOf = (clip) => {
5024
+ let found = mirrored.get(clip.id);
5025
+ if (!found) mirrored.set(clip.id, found = mirrorPoseClip(clip, `${clip.id}_left`));
5026
+ return found;
5027
+ };
5028
+ return {
5029
+ root: avatar.root,
5030
+ avatar,
5031
+ arms,
5032
+ ...arms ? { firstPerson: { root: arms.root, update: (dt, state) => arms.update(dt, state) } } : {},
5033
+ update: (dt, state) => avatar.update(dt, { speed: state.speed, running: state.running, crouch: state.crouch, down: state.down, armed: state.armed, pitch: -state.pitch }),
5034
+ hand: (side) => avatar.hand(side),
5035
+ strike: (clipId, strikeOptions) => {
5036
+ const clip = strikes.get(clipId);
5037
+ if (!clip) return null;
5038
+ const left = strikeOptions?.leg === "left";
5039
+ avatar.play(left ? leftOf(clip) : clip);
5040
+ arms?.play(clipId, left ? { leg: "left" } : void 0);
5041
+ return impactOf(clip);
5042
+ }
5043
+ };
5044
+ };
5045
+
5046
+ // src/player/test-module.ts
5047
+ import { POSE_CLIPS as POSE_CLIPS2, POSE_CLIP_LOCALES } from "@adatechnology/game-characters";
5048
+
5049
+ // src/player/test-text.ts
5050
+ var AVATAR_TEXT = {
5051
+ en: {
5052
+ group: "Avatar",
5053
+ name: "Player body",
5054
+ actions: {
5055
+ camera_first: "Camera: 1st person",
5056
+ camera_third: "Camera: 3rd person",
5057
+ crouch: "Crouch",
5058
+ stand: "Stand up",
5059
+ fall: "Fall down",
5060
+ get_up: "Get up",
5061
+ armed_on: "Armed: on",
5062
+ armed_off: "Armed: off",
5063
+ stop_clip: "Stop clip"
5064
+ },
5065
+ play: "Play",
5066
+ metrics: { camera: "Camera", clip: "Clip playing", last_event: "Last clip event", state: "State", joints: "Joints" },
5067
+ none: "none"
5068
+ },
5069
+ "pt-BR": {
5070
+ group: "Avatar",
5071
+ name: "Corpo do jogador",
5072
+ actions: {
5073
+ camera_first: "C\xE2mera: 1\xAA pessoa",
5074
+ camera_third: "C\xE2mera: 3\xAA pessoa",
5075
+ crouch: "Agachar",
5076
+ stand: "Levantar",
5077
+ fall: "Cair",
5078
+ get_up: "Levantar do ch\xE3o",
5079
+ armed_on: "Armado: ligado",
5080
+ armed_off: "Armado: desligado",
5081
+ stop_clip: "Parar clipe"
5082
+ },
5083
+ play: "Tocar",
5084
+ metrics: { camera: "C\xE2mera", clip: "Clipe tocando", last_event: "\xDAltimo evento do clipe", state: "Estado", joints: "Juntas" },
5085
+ none: "nenhum"
5086
+ }
5087
+ };
5088
+
5089
+ // src/player/test-module.ts
5090
+ var AVATAR_AREA_ID = "avatar_area";
5091
+ var round = (value) => (Math.round(value * 100) / 100).toString();
5092
+ function createAvatarTestModule(options) {
5093
+ const clips = options.clips ?? POSE_CLIPS2;
5094
+ const textOf = (locale) => AVATAR_TEXT[locale ?? options.locale ?? "pt-BR"] ?? AVATAR_TEXT["pt-BR"];
5095
+ const phaseLabel = (label, locale) => {
5096
+ const id = locale ?? options.locale ?? "pt-BR";
5097
+ const table = { ...POSE_CLIP_LOCALES[id] ?? POSE_CLIP_LOCALES["pt-BR"], ...options.clipLocales?.[id] ?? options.clipLocales?.["pt-BR"] };
5098
+ return typeof label === "string" ? table[label] ?? label : "";
5099
+ };
5100
+ const state = { crouch: false, down: false, armed: null };
5101
+ let host = null;
5102
+ let lastEvent = "";
5103
+ const log = (action, extra = {}) => {
5104
+ const entry = { type: "avatar", action, ...extra };
5105
+ host?.log(JSON.stringify(entry), entry);
5106
+ };
5107
+ const push = (action, patch, extra = {}) => {
5108
+ Object.assign(state, patch);
5109
+ options.state?.set(patch);
5110
+ log(action, { crouch: state.crouch, down: state.down, armed: state.armed, ...extra });
5111
+ };
5112
+ const setCamera = (mode) => {
5113
+ options.camera?.set(mode);
5114
+ log(`camera_${mode}`, { mode: options.camera?.mode() ?? null });
5115
+ };
5116
+ const onChange = (change) => {
5117
+ if (change.kind !== "action") return;
5118
+ switch (change.id) {
5119
+ case "camera_first":
5120
+ return setCamera("first");
5121
+ case "camera_third":
5122
+ return setCamera("third");
5123
+ case "crouch":
5124
+ return push("crouch", { crouch: true });
5125
+ case "stand":
5126
+ return push("stand", { crouch: false });
5127
+ case "fall":
5128
+ return push("fall", { down: true });
5129
+ case "get_up":
5130
+ return push("get_up", { down: false });
5131
+ case "armed_on":
5132
+ return push("armed_on", { armed: true });
5133
+ case "armed_off":
5134
+ return push("armed_off", { armed: false });
5135
+ case "stop_clip":
5136
+ options.avatar.stop();
5137
+ return log("stop_clip", { playing: options.avatar.playing });
5138
+ default: {
5139
+ const clip = clips.find((item) => `play_${item.id}` === change.id);
5140
+ if (!clip) return;
5141
+ options.avatar.play(clip);
5142
+ log("play_clip", { clip: clip.id, duration: clip.duration, loop: clip.loop ?? false, channels: clip.channels ?? "full" });
5143
+ }
5144
+ }
5145
+ };
5146
+ const joints = () => {
5147
+ const rig = options.avatar.rig;
5148
+ const [armA, armB] = rig.arms;
5149
+ const [legA, legB] = rig.legs;
5150
+ 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)}`;
5151
+ };
5152
+ return {
5153
+ id: AVATAR_AREA_ID,
5154
+ name: textOf().name,
5155
+ group: textOf().group,
5156
+ state,
5157
+ items: () => [],
5158
+ area: () => ({ placements: [], view: { eye: [0, 1.7, 6], target: [0, 1.2, 0] } }),
5159
+ controls: (locale) => {
5160
+ const text = textOf(locale);
5161
+ const action = (id, name, hint) => ({ kind: "action", id, name, ...hint ? { hint } : {} });
5162
+ return [
5163
+ action("camera_first", text.actions.camera_first),
5164
+ action("camera_third", text.actions.camera_third),
5165
+ action("crouch", text.actions.crouch),
5166
+ action("stand", text.actions.stand),
5167
+ action("fall", text.actions.fall),
5168
+ action("get_up", text.actions.get_up),
5169
+ action("armed_on", text.actions.armed_on),
5170
+ action("armed_off", text.actions.armed_off),
5171
+ ...clips.map((clip) => action(`play_${clip.id}`, `${text.play}: ${clip.id}`)),
5172
+ action("stop_clip", text.actions.stop_clip)
5173
+ ];
5174
+ },
5175
+ metrics: (locale) => {
5176
+ const text = textOf(locale);
5177
+ return [
5178
+ { id: "camera", name: text.metrics.camera, read: () => options.camera?.mode() ?? text.none },
5179
+ { id: "clip", name: text.metrics.clip, read: () => options.avatar.playing ?? text.none },
5180
+ { id: "last_event", name: text.metrics.last_event, read: () => lastEvent || text.none },
5181
+ { id: "state", name: text.metrics.state, read: () => `${state.crouch ? "crouch " : ""}${state.down ? "down " : ""}${state.armed === null ? "" : state.armed ? "armed" : "unarmed"}`.trim() || text.none },
5182
+ { id: "joints", name: text.metrics.joints, read: joints }
5183
+ ];
5184
+ },
5185
+ attach(lent) {
5186
+ host = lent;
5187
+ const stopChange = lent.bench?.onChange(onChange);
5188
+ const stopEvents = options.avatar.on("event", (event) => {
5189
+ lastEvent = `${event.clip} ${event.type} ${round(event.t)}s${event.data && "label" in event.data ? ` ${phaseLabel(event.data.label)}` : ""}`;
5190
+ log("clip_event", { clip: event.clip, event: event.type, t: event.t, data: event.data ?? null });
5191
+ if (event.type === "impact") log("impact", { clip: event.clip, t: event.t, kind: event.data?.kind ?? null });
5192
+ });
5193
+ return () => {
5194
+ stopChange?.();
5195
+ stopEvents();
5196
+ host = null;
5197
+ };
5198
+ }
5199
+ };
5200
+ }
4098
5201
  export {
5202
+ AVATAR_AREA_ID,
4099
5203
  BAKE_NEUTRAL_POSE,
4100
5204
  BODY_CHUNK,
4101
5205
  BODY_MATERIAL_DEF,
@@ -4105,6 +5209,10 @@ export {
4105
5209
  CHARACTER_SOUND_BANK,
4106
5210
  EXTRAS,
4107
5211
  FACE_FEATURES,
5212
+ FIRST_PERSON_BOB,
5213
+ FIRST_PERSON_COLORS,
5214
+ FIRST_PERSON_RIG,
5215
+ FIRST_PERSON_STANCES,
4108
5216
  HAND_ITEMS,
4109
5217
  HATS,
4110
5218
  HIT_PRESETS,
@@ -4126,6 +5234,7 @@ export {
4126
5234
  blobRadii,
4127
5235
  bloodLookOfAge,
4128
5236
  buildInstancedBody,
5237
+ createAvatarTestModule,
4129
5238
  createBestBuilder,
4130
5239
  createBodyBatch,
4131
5240
  createBodyChunk,
@@ -4133,12 +5242,14 @@ export {
4133
5242
  createCharacterSound,
4134
5243
  createCharactersTestModule,
4135
5244
  createEye,
5245
+ createFirstPersonArms,
4136
5246
  createFluidsBridge,
4137
5247
  createFlyingLimbs,
4138
5248
  createFlyingPieces,
4139
5249
  createGlowTexture,
4140
5250
  createPendulum,
4141
5251
  createPersonBuilder,
5252
+ createPlayerAvatar,
4142
5253
  createRigAnimator,
4143
5254
  createVariantBatches,
4144
5255
  createWoundMaterials,
@@ -4153,8 +5264,10 @@ export {
4153
5264
  makeStump,
4154
5265
  registerBodyMaterial,
4155
5266
  ringWound,
5267
+ solveTwoBone,
4156
5268
  starGeometry,
4157
5269
  stepPendulum,
5270
+ toRoomAvatar,
4158
5271
  woundBodyOf,
4159
5272
  woundDecal
4160
5273
  };