@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/README.md +31 -0
- package/dist/index.d.ts +328 -10
- package/dist/index.global.js +3549 -172
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +1128 -15
- package/dist/index.js.map +1 -1
- package/package.json +8 -7
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 = (
|
|
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 = (
|
|
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 = (
|
|
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 = (
|
|
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(
|
|
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
|
-
|
|
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
|
|
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(
|
|
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 = (
|
|
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(
|
|
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 = (
|
|
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
|
};
|