Initial commit

This commit is contained in:
ryanfitzpatrickio
2026-08-03 06:43:21 -05:00
commit 7ee3e9d02f
63 changed files with 15792 additions and 0 deletions
+223
View File
@@ -0,0 +1,223 @@
import { E } from '../../core/math.js';
import { clamp, lerp } from '../../../shared/scalar.js';
/**
* Upper-body authoring for everything done with the stick.
*
* These are *override* poses, not whole-body states. They write the arms and
* some spine, and the animator blends them over the skating pose by a weight —
* because you keep skating while you shoot, and a shot that stopped the legs
* would read as a cutscene.
*
* Each one is a function of a single phase 0..1 so the animator can drive it
* from a timer, hold it (wind-up), or run it once and blend out (shoot, pass,
* poke). The right arm carries the stick; the left joins it for two-handed
* work and is pinned onto the shaft by IK afterwards, so what is authored here
* for the left side is only a starting guess that the IK refines.
*/
/**
* Bones the stickwork layer *replaces*. The arms belong to the stick whenever
* it is being used — there is no meaningful blend between "swinging with the
* stride" and "holding a stick", they are different arms.
*/
export const STICK_ARMS = [
'clavicleR', 'upperArmR', 'forearmR', 'handR',
'clavicleL', 'upperArmL', 'forearmL', 'handL',
];
/**
* Bones the layer *adds to*. The spine is already carrying the skating lean and
* the bank; a shot's coil is a twist on top of that, not instead of it.
* Replacing these was what flattened the forward lean the moment a stick
* appeared, and stood everybody up.
*/
export const STICK_SPINE = ['spine1', 'spine2', 'spine3', 'neck', 'head'];
export const STICK_BONES = STICK_ARMS.concat(STICK_SPINE);
/**
* The neutral carry, and the hustle variant.
*
* `hustle` 0..1 slides between two-hands-ready and the one-handed dangle a
* skater falls into when they are just trying to move: the stick goes out in
* front, the left arm leaves it and swings with the stride.
*
* Carry is authored from the motion-reference sheet (ready stance / puck carry):
* both hands in front of the torso, shaft angled down to the ice, blade a
* little to the forehand side — not parked out on the hip with the off-hand
* floating. The right arm has to sit close enough that the left can actually
* reach the shaft: the arm is only ~0.54 m long, so a top hand 40 cm off
* centre puts the stick out of reach no matter what the IK does.
*/
export function poseCarry(P, { hustle = 0, reach = 0, lateral = 0 }) {
const h = clamp(hustle, 0, 1);
// Skill Stick +X is "push right"; bone +X is the skater's left. Negate so
// the arms lean the same way the blade goes.
const side = -lateral;
// Right arm: top hand. Across the body and out in front at about waist /
// lower-chest height. Hustle extends it forward and frees the left side.
// Roll signs are mirrored: right arm tucks on *positive* Z, left on negative.
E(P.q.clavicleR, 0.03, lerp(-0.04, -0.1, h), 0.06);
E(
P.q.upperArmR,
lerp(-0.42, -0.7, h) + reach * 0.2,
lerp(0.42, 0.05, h) + side * 0.28,
lerp(0.68, 0.38, h),
);
E(
P.q.forearmR,
lerp(-1.35, -0.75, h) - reach * 0.15,
lerp(0.02, 0.12, h),
lerp(0.32, 0.16, h),
);
E(P.q.handR, lerp(-0.12, -0.08, h), lerp(0.12, 0.04, h), lerp(0.04, -0.12, h));
// Left arm: lower hand on the shaft when settled. Seeded near the stick so
// the IK only has to finish the last few centimetres, not haul it across the
// body. At full hustle it leaves the stick and opens for the stride swing.
E(P.q.clavicleL, 0.03, lerp(0.06, 0.02, h), lerp(-0.06, -0.02, h));
E(
P.q.upperArmL,
lerp(-0.38, -0.66, h) + reach * 0.12,
lerp(0.1, 0.16, h) + side * 0.18,
lerp(-0.48, -0.2, h),
);
E(
P.q.forearmL,
lerp(-1.32, -0.72, h),
lerp(-0.08, 0, h),
lerp(0.18, 0.08, h),
);
E(P.q.handL, -0.1, 0, 0.08 * (1 - h));
// Soft coil over the stick when both hands are on it; opens up when hustling.
E(P.q.spine1, 0.02 * h, lerp(-0.04, 0.02, h) + side * 0.05, 0);
E(P.q.spine2, 0.02 * h, lerp(-0.05, 0.02, h) + side * 0.05, 0);
E(P.q.spine3, 0.02, lerp(-0.04, 0, h), 0);
}
/**
* Wind-up. `phase` 0..1 is how loaded the shot is, and it is *held* — the
* animator parks here for as long as the Skill Stick is pulled back.
*
* Hands high and back, stick raised behind the head — not hanging blade-down
* from waist height. The torso coils open so the follow-through has something
* to spend.
*/
export function poseWindup(P, { phase = 0, aim = 0 }) {
const w = clamp(phase, 0, 1);
// Aim on the Skill Stick is "push right"; bone +Y twist toward the skater's
// left is the opposite sign.
const side = -aim;
// Torso coils open, loading the shot side.
E(P.q.spine1, -0.06 - 0.08 * w, -0.18 - 0.42 * w + side * 0.08, -0.05 * w);
E(P.q.spine2, -0.07 - 0.1 * w, -0.22 - 0.48 * w + side * 0.1, -0.06 * w);
E(P.q.spine3, -0.04 - 0.07 * w, -0.18 - 0.38 * w + side * 0.08, -0.04 * w);
// Eyes stay on the target while the body turns away from it.
E(P.q.neck, 0.04, 0.28 + 0.42 * w - side * 0.2, 0);
E(P.q.head, 0.04, 0.22 + 0.32 * w - side * 0.25, 0);
// Top hand: high and back, roughly shoulder/head height, so the aimed stick
// can sit up behind the head instead of dangling at the hip.
E(P.q.clavicleR, -0.1 * w, -0.22 * w, -0.1);
E(P.q.upperArmR, 0.15 + 0.65 * w, -0.55 - 0.35 * w + side * 0.15, -0.55 - 0.35 * w);
E(P.q.forearmR, -0.45 - 0.25 * w, 0.22, -0.12);
E(P.q.handR, -0.05, 0.2, 0.22);
// Lower hand comes up with it; IK pins it to the shaft.
E(P.q.clavicleL, 0.04, 0.12 * w, 0.08);
E(P.q.upperArmL, -0.35 - 0.1 * w, 0.45 + 0.2 * w, 0.4 + 0.15 * w);
E(P.q.forearmL, -0.85 - 0.15 * w, -0.18, -0.12);
E(P.q.handL, -0.08, 0, -0.1);
}
/**
* Follow-through. `phase` 0..1 runs once, fast.
*
* The coil released: the torso whips through the shot, the stick sweeps across
* and finishes high. Front-loaded easing, so the contact reads at the start of
* the animation rather than in the middle of it.
*/
export function poseShot(P, { phase = 0, power = 1, aim = 0 }) {
const t = clamp(phase, 0, 1);
// Fast out of the coil, then settle.
const s = 1 - (1 - t) * (1 - t);
const p = clamp(power, 0.2, 1);
const twist = lerp(-0.42 * p, 0.44 * p, s);
E(P.q.spine1, -0.06 + 0.12 * s, twist * 0.9, 0.04 * s);
E(P.q.spine2, -0.07 + 0.14 * s, twist, 0.05 * s);
E(P.q.spine3, -0.05 + 0.1 * s, twist * 0.8, 0.03 * s);
E(P.q.neck, 0.02, -twist * 0.5 + aim * 0.2, 0);
E(P.q.head, 0.02, -twist * 0.4 + aim * 0.25, 0);
// Top hand drives through and finishes high across the body.
E(P.q.clavicleR, lerp(-0.05, 0.04, s), lerp(-0.14, 0.1, s), -0.06);
E(P.q.upperArmR, lerp(0.3, -1.05 * p, s), lerp(-0.94, 0.3, s), lerp(-0.72, -0.1, s));
E(P.q.forearmR, lerp(-1.12, -0.42, s), 0.16, -0.1);
E(P.q.handR, -0.1, 0.1, 0.16);
E(P.q.clavicleL, 0.03, lerp(0.1, -0.04, s), 0.06);
E(P.q.upperArmL, lerp(-0.86, -0.3, s), lerp(0.66, 0.12, s), lerp(0.4, 0.5, s));
E(P.q.forearmL, lerp(-1.36, -0.6, s), -0.28, -0.2);
E(P.q.handL, -0.08, 0, -0.14);
}
/**
* Pass: a flat sweep, no coil and no lift. Shorter and lower than a shot,
* because a pass that looks like a shot makes the two impossible to read apart
* at a glance — which matters more for a teammate watching than for the passer.
*/
export function posePass(P, { phase = 0, aim = 0 }) {
const t = clamp(phase, 0, 1);
const s = Math.sin(t * Math.PI); // out and back
const sweep = lerp(-0.18, 0.34, 1 - (1 - t) * (1 - t));
E(P.q.spine1, 0.03 * s, sweep * 0.6, 0);
E(P.q.spine2, 0.04 * s, sweep * 0.7, 0);
E(P.q.spine3, 0.03 * s, sweep * 0.5, 0);
E(P.q.neck, 0, -sweep * 0.4 + aim * 0.2, 0);
E(P.q.head, 0, -sweep * 0.3 + aim * 0.2, 0);
E(P.q.clavicleR, 0.02, -0.04, -0.05);
E(P.q.upperArmR, -0.34 - 0.3 * s, -0.34 + sweep * 0.5, -0.4 - 0.12 * s);
E(P.q.forearmR, -0.72 - 0.24 * s, 0.12, -0.12);
E(P.q.handR, -0.12, 0.06, 0.18);
E(P.q.clavicleL, 0.02, 0.05, 0.05);
E(P.q.upperArmL, -0.58 - 0.18 * s, 0.36 + sweep * 0.3, 0.3);
E(P.q.forearmL, -1.06 - 0.16 * s, -0.24, -0.18);
E(P.q.handL, -0.1, 0, -0.12);
}
/**
* Poke check: a stab. One hand, the whole arm extending forward with the body
* reaching after it, back almost as fast as it went out.
*/
export function posePoke(P, { phase = 0 }) {
const t = clamp(phase, 0, 1);
// Out fast, back slower.
const s = t < 0.35 ? t / 0.35 : 1 - (t - 0.35) / 0.65;
const jab = clamp(s, 0, 1);
E(P.q.spine1, 0.06 * jab, -0.14 * jab, 0);
E(P.q.spine2, 0.07 * jab, -0.18 * jab, 0);
E(P.q.spine3, 0.05 * jab, -0.14 * jab, 0);
E(P.q.neck, -0.04 * jab, 0.1 * jab, 0);
E(P.q.head, -0.04 * jab, 0.1 * jab, 0);
// Right arm thrusts out and down toward the ice.
E(P.q.clavicleR, 0.02 + 0.06 * jab, -0.06 - 0.16 * jab, -0.05);
E(P.q.upperArmR, -0.34 - 0.5 * jab, -0.28 - 0.12 * jab, -0.36 + 0.14 * jab);
E(P.q.forearmR, -0.78 + 0.66 * jab, 0.1, -0.12);
E(P.q.handR, -0.12, 0.06, 0.18);
// Left arm comes off the stick and back for balance.
E(P.q.clavicleL, 0.02, 0.04, 0.04);
E(P.q.upperArmL, -0.5 + 0.2 * jab, 0.28 - 0.2 * jab, 0.34 + 0.16 * jab);
E(P.q.forearmL, -0.9 + 0.3 * jab, -0.16, -0.14);
E(P.q.handL, -0.1, 0, -0.1);
}