@adatechnology/game-characters-three 0.5.0 → 0.6.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
@@ -4235,6 +4235,176 @@ var player_avatar_default = {
4235
4235
  shirtColor: "3d5a73"
4236
4236
  };
4237
4237
 
4238
+ // src/first-person/ik.ts
4239
+ var solveTwoBone = (root, target, upper, lower, pole, out) => {
4240
+ const line = out.line.copy(target).sub(root);
4241
+ let distance2 = line.length();
4242
+ if (distance2 < 1e-6) line.set(0, 0, -1);
4243
+ else line.divideScalar(distance2);
4244
+ const min = Math.abs(upper - lower) + 1e-4;
4245
+ const max = upper + lower - 1e-4;
4246
+ out.clamped = distance2 > max || distance2 < min;
4247
+ distance2 = Math.min(max, Math.max(min, distance2));
4248
+ const along = (distance2 * distance2 + upper * upper - lower * lower) / (2 * distance2);
4249
+ const across = Math.sqrt(Math.max(0, upper * upper - along * along));
4250
+ const side = out.side.copy(pole);
4251
+ side.addScaledVector(line, -side.dot(line));
4252
+ if (side.lengthSq() < 1e-8) side.set(0, 1, 0).addScaledVector(line, -line.y);
4253
+ if (side.lengthSq() < 1e-8) side.set(1, 0, 0);
4254
+ side.normalize();
4255
+ out.joint.copy(root).addScaledVector(line, along).addScaledVector(side, across);
4256
+ out.end.copy(root).addScaledVector(line, distance2);
4257
+ return out;
4258
+ };
4259
+ var createTwoBone = (THREE) => ({ joint: new THREE.Vector3(), end: new THREE.Vector3(), clamped: false, line: new THREE.Vector3(), side: new THREE.Vector3() });
4260
+
4261
+ // src/player/arm-reach.ts
4262
+ var ARM_REACH = {
4263
+ /** The elbow never straightens past this bend (a locked elbow reads as a stick). */
4264
+ elbowMin: 0.12,
4265
+ /** Full flexion of the elbow. */
4266
+ elbowMax: 2.5,
4267
+ /** The wrist bends (flexion, extension, deviation) up to this angle from the line of the forearm (flexion reaches about 80 degrees). */
4268
+ wristBend: 1.3,
4269
+ /** The forearm turns (pronation and supination) up to this angle about its own axis. */
4270
+ wristTwist: 1.6,
4271
+ /** The most the chest turns to bring the support shoulder toward a far hand. */
4272
+ chestTurn: 0.6,
4273
+ /** The most a shoulder blade slides toward a far hand (protraction and elevation together). */
4274
+ protraction: 0.11,
4275
+ /** A hand is within reach below this fraction of the arm's length. */
4276
+ slack: 0.96,
4277
+ /** Where the elbow points: down and out from the body, a little back (body space, x outward). */
4278
+ elbowPole: [0.55, -1, -0.25],
4279
+ /** From the wrist joint to the middle of the palm, metres of the body. */
4280
+ palm: 0.042
4281
+ };
4282
+ var createArmReach = (THREE, rig, hands) => {
4283
+ const arms = {
4284
+ R: { g: rig.arms[1].g, el: rig.arms[1].el, hand: hands.R, outward: -1 },
4285
+ L: { g: rig.arms[0].g, el: rig.arms[0].el, hand: hands.L, outward: 1 }
4286
+ };
4287
+ const upper = rig.upper;
4288
+ const handBasis = {
4289
+ R: new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().makeBasis(new THREE.Vector3(0, -1, 0), new THREE.Vector3(-1, 0, 0), new THREE.Vector3(0, 0, -1))).invert(),
4290
+ L: new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().makeBasis(new THREE.Vector3(0, -1, 0), new THREE.Vector3(1, 0, 0), new THREE.Vector3(0, 0, 1))).invert()
4291
+ };
4292
+ const saved = {
4293
+ twist: 0,
4294
+ arms: { R: { position: new THREE.Vector3(), g: new THREE.Quaternion(), el: new THREE.Quaternion(), hand: new THREE.Quaternion() }, L: { position: new THREE.Vector3(), g: new THREE.Quaternion(), el: new THREE.Quaternion(), hand: new THREE.Quaternion() } }
4295
+ };
4296
+ let applied = false;
4297
+ const bone = createTwoBone(THREE);
4298
+ 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
+ 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
+ const basis = new THREE.Matrix4();
4301
+ const yAxis = new THREE.Vector3(0, 1, 0);
4302
+ const lengthsOf = (arm) => {
4303
+ const scale = arm.g.getWorldScale(v.a).y;
4304
+ return { upper: arm.el.position.length() * scale, fore: arm.hand.position.length() * scale, palm: ARM_REACH.palm * scale };
4305
+ };
4306
+ const wristOf = (side, pose, palm) => {
4307
+ q.hand.set(...pose.quaternion).multiply(handBasis[side]);
4308
+ v.palm.set(...pose.position);
4309
+ v.wrist.copy(v.palm).addScaledVector(v.a.set(0, -1, 0).applyQuaternion(q.hand), -palm);
4310
+ };
4311
+ const turnChest = (pose) => {
4312
+ const { upper: up, fore, palm } = lengthsOf(arms.L);
4313
+ wristOf("L", pose, palm);
4314
+ const excess = arms.L.g.getWorldPosition(v.shoulder).distanceTo(v.wrist) - (up + fore) * ARM_REACH.slack;
4315
+ if (excess <= 0) return;
4316
+ const chestRadius = Math.hypot(arms.L.g.position.x, arms.L.g.position.z) * arms.L.g.getWorldScale(v.a).y;
4317
+ upper.rotation.y -= Math.min(ARM_REACH.chestTurn, excess / Math.max(0.05, chestRadius));
4318
+ upper.updateMatrixWorld(true);
4319
+ };
4320
+ const protract = (arm, length) => {
4321
+ const excess = arm.g.getWorldPosition(v.shoulder).distanceTo(v.wrist) - length * ARM_REACH.slack;
4322
+ if (excess <= 0) return;
4323
+ const parent = arm.g.parent;
4324
+ v.b.copy(v.wrist).sub(v.shoulder).normalize().multiplyScalar(Math.min(excess, ARM_REACH.protraction)).add(v.shoulder);
4325
+ arm.g.position.copy(parent.worldToLocal(v.b));
4326
+ arm.g.updateMatrixWorld(true);
4327
+ };
4328
+ const limitWrist = (local) => {
4329
+ const projection = v.c.set(local.x, local.y, local.z).projectOnVector(yAxis);
4330
+ q.twist.set(projection.x, projection.y, projection.z, local.w).normalize();
4331
+ const twistAngle = 2 * Math.atan2(q.twist.y, q.twist.w);
4332
+ const swing = q.swing.copy(local).multiply(q.twist.invert());
4333
+ const swingAngle = 2 * Math.acos(Math.min(1, Math.abs(swing.w)));
4334
+ if (swingAngle > ARM_REACH.wristBend) swing.slerp(q.rest.identity(), 1 - ARM_REACH.wristBend / swingAngle);
4335
+ const clampedTwist = Math.max(-ARM_REACH.wristTwist, Math.min(ARM_REACH.wristTwist, Math.atan2(Math.sin(twistAngle), Math.cos(twistAngle))));
4336
+ return local.copy(swing).multiply(q.twist.setFromAxisAngle(yAxis, clampedTwist));
4337
+ };
4338
+ const reach = (side, pose) => {
4339
+ const arm = arms[side];
4340
+ const { upper: up, fore, palm } = lengthsOf(arm);
4341
+ wristOf(side, pose, palm);
4342
+ protract(arm, up + fore);
4343
+ arm.g.getWorldPosition(v.shoulder);
4344
+ 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));
4347
+ const least = Math.sqrt(up * up + fore * fore + 2 * up * fore * Math.cos(ARM_REACH.elbowMax));
4348
+ const line = v.c.copy(v.wrist).sub(v.shoulder);
4349
+ const distance2 = Math.min(most, Math.max(least, line.length()));
4350
+ v.target.copy(v.shoulder).addScaledVector(line.normalize(), distance2);
4351
+ solveTwoBone(v.shoulder, v.target, up, fore, v.pole, bone);
4352
+ const along = v.a.copy(bone.joint).sub(v.shoulder).normalize();
4353
+ const forearm = v.b.copy(bone.end).sub(bone.joint).normalize();
4354
+ const fold = v.c.copy(forearm).addScaledVector(along, -forearm.dot(along));
4355
+ if (fold.lengthSq() < 1e-8) fold.copy(v.pole).addScaledVector(along, -v.pole.dot(along)).negate();
4356
+ fold.normalize();
4357
+ const yWorld = v.elbow.copy(along).negate();
4358
+ basis.makeBasis(v.pole.crossVectors(yWorld, fold), yWorld, fold);
4359
+ arm.g.parent.getWorldQuaternion(q.parent);
4360
+ arm.g.quaternion.setFromRotationMatrix(basis).premultiply(q.parent.invert());
4361
+ arm.el.rotation.set(-Math.acos(Math.max(-1, Math.min(1, along.dot(forearm)))), 0, 0);
4362
+ arm.g.updateMatrixWorld(true);
4363
+ const reached = arm.hand.getWorldPosition(v.a).sub(v.shoulder).normalize();
4364
+ q.swing.setFromUnitVectors(reached, v.b.copy(v.target).sub(v.shoulder).normalize());
4365
+ arm.g.parent.getWorldQuaternion(q.parent);
4366
+ arm.g.quaternion.premultiply(q.parent).premultiply(q.swing).premultiply(q.parent.invert());
4367
+ arm.g.updateMatrixWorld(true);
4368
+ arm.el.getWorldQuaternion(q.parent);
4369
+ arm.hand.quaternion.copy(limitWrist(q.local.copy(q.parent.invert()).multiply(q.hand)));
4370
+ };
4371
+ const save = () => {
4372
+ saved.twist = upper.rotation.y;
4373
+ for (const side of ["R", "L"]) {
4374
+ const arm = arms[side];
4375
+ const into = saved.arms[side];
4376
+ into.position.copy(arm.g.position);
4377
+ into.g.copy(arm.g.quaternion);
4378
+ into.el.copy(arm.el.quaternion);
4379
+ into.hand.copy(arm.hand.quaternion);
4380
+ }
4381
+ };
4382
+ return {
4383
+ apply(targets) {
4384
+ if (!targets || !targets.R && !targets.L) return;
4385
+ save();
4386
+ applied = true;
4387
+ 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);
4391
+ },
4392
+ restore() {
4393
+ if (!applied) return;
4394
+ applied = false;
4395
+ upper.rotation.y = saved.twist;
4396
+ for (const side of ["R", "L"]) {
4397
+ const arm = arms[side];
4398
+ const from = saved.arms[side];
4399
+ arm.g.position.copy(from.position);
4400
+ arm.g.quaternion.copy(from.g);
4401
+ arm.el.quaternion.copy(from.el);
4402
+ arm.hand.quaternion.copy(from.hand);
4403
+ }
4404
+ }
4405
+ };
4406
+ };
4407
+
4238
4408
  // src/player/avatar.ts
4239
4409
  var TWO_PI = Math.PI * 2;
4240
4410
  var MAX_GAIT = 1.4;
@@ -4331,6 +4501,7 @@ var createPlayerAvatar = (THREE, options = {}) => {
4331
4501
  const built = buildHand({ THREE, add: addMesh, hg, handM: handMaterial, s: side, detailed: true });
4332
4502
  hands.push({ side, hg, fingers: built.fingers, th: built.th });
4333
4503
  });
4504
+ const armReach = createArmReach(THREE, rig, { R: hands.find((hand) => hand.side < 0).hg, L: hands.find((hand) => hand.side > 0).hg });
4334
4505
  const setHandGrip = (hand, grip, wristX = 0) => {
4335
4506
  hand.fingers.forEach((finger, j) => {
4336
4507
  finger.rotation.z = -hand.side * grip * (j % 2 ? 1.1 : 0.9);
@@ -4561,6 +4732,7 @@ var createPlayerAvatar = (THREE, options = {}) => {
4561
4732
  return run ? run.clip.id : null;
4562
4733
  },
4563
4734
  update(dt, state = {}) {
4735
+ armReach.restore();
4564
4736
  const speed = state.speed ?? 0;
4565
4737
  const moving = speed >= 0.05;
4566
4738
  const down = !!state.down;
@@ -4614,6 +4786,11 @@ var createPlayerAvatar = (THREE, options = {}) => {
4614
4786
  if (run) run.target = 0;
4615
4787
  },
4616
4788
  hand: (side) => sockets[side],
4789
+ reach(targets) {
4790
+ if (run || fall > 0) return;
4791
+ root.updateMatrixWorld(true);
4792
+ armReach.apply(targets);
4793
+ },
4617
4794
  on(_type, listener) {
4618
4795
  listeners.add(listener);
4619
4796
  return () => void listeners.delete(listener);
@@ -4775,29 +4952,6 @@ var createHandMesh = (THREE, skin, wristSkin = skin) => {
4775
4952
  };
4776
4953
  };
4777
4954
 
4778
- // src/first-person/ik.ts
4779
- var solveTwoBone = (root, target, upper, lower, pole, out) => {
4780
- const line = out.line.copy(target).sub(root);
4781
- let distance2 = line.length();
4782
- if (distance2 < 1e-6) line.set(0, 0, -1);
4783
- else line.divideScalar(distance2);
4784
- const min = Math.abs(upper - lower) + 1e-4;
4785
- const max = upper + lower - 1e-4;
4786
- out.clamped = distance2 > max || distance2 < min;
4787
- distance2 = Math.min(max, Math.max(min, distance2));
4788
- const along = (distance2 * distance2 + upper * upper - lower * lower) / (2 * distance2);
4789
- const across = Math.sqrt(Math.max(0, upper * upper - along * along));
4790
- const side = out.side.copy(pole);
4791
- side.addScaledVector(line, -side.dot(line));
4792
- if (side.lengthSq() < 1e-8) side.set(0, 1, 0).addScaledVector(line, -line.y);
4793
- if (side.lengthSq() < 1e-8) side.set(1, 0, 0);
4794
- side.normalize();
4795
- out.joint.copy(root).addScaledVector(line, along).addScaledVector(side, across);
4796
- out.end.copy(root).addScaledVector(line, distance2);
4797
- return out;
4798
- };
4799
- var createTwoBone = (THREE) => ({ joint: new THREE.Vector3(), end: new THREE.Vector3(), clamped: false, line: new THREE.Vector3(), side: new THREE.Vector3() });
4800
-
4801
4955
  // src/first-person/arms.ts
4802
4956
  var DEFAULT_BLEND = 0.18;
4803
4957
  var ELBOW_SEGMENTS = 8;
@@ -5263,6 +5417,7 @@ var toRoomAvatar = (avatar, options = {}) => {
5263
5417
  });
5264
5418
  },
5265
5419
  hand: (side) => avatar.hand(side),
5420
+ reach: (targets) => avatar.reach(targets),
5266
5421
  strike: (clipId, strikeOptions) => {
5267
5422
  const clip = strikes.get(clipId);
5268
5423
  if (!clip) return null;