@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.d.ts +7 -0
- package/dist/index.global.js +178 -23
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +178 -23
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
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;
|