@rig-c/pose 0.38.0 → 0.39.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 +40 -6
- package/cli.ts +33 -5
- package/package.json +1 -1
- package/src/clean.ts +24 -2
- package/src/depth.ts +190 -0
- package/src/legs.ts +189 -0
- package/src/outdir.ts +54 -0
- package/src/pelvis.ts +100 -15
- package/src/pose.ts +150 -18
- package/src/run.ts +74 -18
- package/src/runcompare.ts +6 -7
- package/src/verify.ts +90 -9
package/src/verify.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { poseRawAnimation, poseRawSetup, type CompiledDocument, type RawBone } from 'rig-c/core';
|
|
12
12
|
import type { Body18 } from './joints.ts';
|
|
13
|
-
import { aimAngle, aimEnds, pointAt, type Chain, type JointMap } from './map.ts';
|
|
13
|
+
import { aimAngle, aimEnds, partBone, pointAt, pointParts, type Chain, type JointMap } from './map.ts';
|
|
14
14
|
import type { HipsInferred, Joint, PelvisFit, Run } from './pose.ts';
|
|
15
15
|
import type { Retarget } from './retarget.ts';
|
|
16
16
|
import { meanSecondDiff, pictureAngle, pointOn, wrap } from './retarget.ts';
|
|
@@ -311,6 +311,12 @@ export interface Posture {
|
|
|
311
311
|
tilt: { reference: Range; rig: Range; ratio: number } | null;
|
|
312
312
|
/** Present only when `tilt` is null: why it was not read. */
|
|
313
313
|
tiltWhy?: string;
|
|
314
|
+
/**
|
|
315
|
+
* rig-c/pose#295: present only when the retarget keyed the pelvis bone from the
|
|
316
|
+
* sequence's pelvis track: the reference tilt is the track's on every frame it
|
|
317
|
+
* read (`frames`), and the hip line's on the frames it declined (`declined`).
|
|
318
|
+
*/
|
|
319
|
+
tiltTrack?: { source: string; frames: number; declined: Run[] };
|
|
314
320
|
/**
|
|
315
321
|
* Present only when some frame with both hips good has an `inferred` hip:
|
|
316
322
|
* how many such frames the tilt left out, and where the state came from.
|
|
@@ -345,6 +351,15 @@ export interface Posture {
|
|
|
345
351
|
rig: (Range & { shoulderWidth: number | null; rangeSw: number | null }) | null;
|
|
346
352
|
ratio: number | null;
|
|
347
353
|
ratioSw: number | null;
|
|
354
|
+
/**
|
|
355
|
+
* rig-c/pose#295: present only when the pelvis bone keyed from the track
|
|
356
|
+
* (`bone`, `source`) is above the rig's mid-shoulder: the bones it carries
|
|
357
|
+
* (`origins`, its children on the way to the mid-shoulder), the x-range of
|
|
358
|
+
* their origin about the pelvis bone's (`carry`, units), and the sway with
|
|
359
|
+
* that carry taken out — mid_shoulder about the carried origin (`trunk`),
|
|
360
|
+
* against the reference (`ratio`).
|
|
361
|
+
*/
|
|
362
|
+
carried?: { bone: string; source: string; origins: string[]; carry: number; trunk: Range; ratio: number | null };
|
|
348
363
|
} | null;
|
|
349
364
|
/**
|
|
350
365
|
* Present with `--plant-feet` when there is at least one support span (#107):
|
|
@@ -461,8 +476,14 @@ const medianOf = (xs: number[]): number => {
|
|
|
461
476
|
* held from the earlier read. Those four are the measurement, so the tilt is
|
|
462
477
|
* read on them; any `unmeasured` frame (`fit.corr < 0.9`) or other frame with
|
|
463
478
|
* an inferred hip is left out and counted.
|
|
479
|
+
*
|
|
480
|
+
* The pelvis track (#295, `track`): when the retarget keyed the pelvis bone from
|
|
481
|
+
* it, the reference tilt is the track's on every frame it read and the hip
|
|
482
|
+
* line's (as above) on the frames it declined. The sway is read as before, and
|
|
483
|
+
* beside it what the track's turn carries: the x-range of the carried bones'
|
|
484
|
+
* origin about the pelvis bone's, and the sway with it taken out.
|
|
464
485
|
*/
|
|
465
|
-
export function measurePosture(posed: Array<Map<string, RawBone>>, lengths: ReadonlyMap<string, number>, map: JointMap, r: Retarget, frames: ReadonlyArray<Partial<Record<Body18, Joint>>>, minScore: number, inferredSource = 'source not stated', measured: Extract<HipsInferred, { by: 'measured' }> | null = null): Posture | null {
|
|
486
|
+
export function measurePosture(posed: Array<Map<string, RawBone>>, lengths: ReadonlyMap<string, number>, map: JointMap, r: Retarget, frames: ReadonlyArray<Partial<Record<Body18, Joint>>>, minScore: number, inferredSource = 'source not stated', measured: Extract<HipsInferred, { by: 'measured' }> | null = null, track: { source: string; tilt: ReadonlyArray<number | null> } | null = null): Posture | null {
|
|
466
487
|
const pelvis = map.chains.find((c) => c.joints.length === 2 && c.joints[0] === 'r_hip' && c.joints[1] === 'l_hip');
|
|
467
488
|
if (pelvis === undefined || map.translate === null || r.origin === null || r.scale === null) return null;
|
|
468
489
|
const s = r.scale.factor;
|
|
@@ -476,10 +497,31 @@ export function measurePosture(posed: Array<Map<string, RawBone>>, lengths: Read
|
|
|
476
497
|
let inferred = 0;
|
|
477
498
|
const inClass = (rs: Run[], i: number) => rs.some(([a, b]) => a <= i && i <= b);
|
|
478
499
|
const counts = { read: 0, interpolated: 0, held: 0, scaled: 0, unmeasured: 0 };
|
|
500
|
+
// rig-c/pose#295: the track the retarget keyed the pelvis bone from (its PELVIS line names it), null otherwise.
|
|
501
|
+
const keyed = track !== null && r.pelvis !== null && r.pelvis.source !== 'hip line' && r.pelvis.bone === pelvis.bones[0] && track.tilt.length === frames.length ? track : null;
|
|
502
|
+
const declined: Run[] = [];
|
|
503
|
+
let onTrack = 0;
|
|
479
504
|
frames.forEach((f, i) => {
|
|
505
|
+
const tv = keyed === null ? null : (keyed.tilt[i] ?? null);
|
|
506
|
+
if (tv !== null) {
|
|
507
|
+
onTrack++;
|
|
508
|
+
refTilt.push(tv);
|
|
509
|
+
} else if (keyed !== null) {
|
|
510
|
+
const last = declined[declined.length - 1];
|
|
511
|
+
if (last !== undefined && last[1] === i - 1) last[1] = i;
|
|
512
|
+
else declined.push([i, i]);
|
|
513
|
+
}
|
|
480
514
|
const rh = at(i, 'r_hip');
|
|
481
515
|
const lh = at(i, 'l_hip');
|
|
482
516
|
if (rh === null || lh === null) return;
|
|
517
|
+
if (tv === null) readHipLine(f, i, rh, lh);
|
|
518
|
+
const rs = at(i, 'r_shoulder');
|
|
519
|
+
const ls = at(i, 'l_shoulder');
|
|
520
|
+
if (rs === null || ls === null) return;
|
|
521
|
+
refSway.push(((rh[0] + lh[0]) / 2 - (rs[0] + ls[0]) / 2) * s);
|
|
522
|
+
refSw.push(Math.hypot(ls[0] - rs[0], ls[1] - rs[1]) * s);
|
|
523
|
+
});
|
|
524
|
+
function readHipLine(f: Partial<Record<Body18, Joint>>, i: number, rh: [number, number], lh: [number, number]): void {
|
|
483
525
|
const isInferred = f.r_hip?.state === 'inferred' || f.l_hip?.state === 'inferred';
|
|
484
526
|
const cls = measured === null ? undefined : (['read', 'interpolated', 'held'] as const).find((c) => inClass(measured[c], i));
|
|
485
527
|
const isScaled = measured !== null && measured.scaled !== undefined && inClass(measured.scaled, i);
|
|
@@ -494,12 +536,7 @@ export function measurePosture(posed: Array<Map<string, RawBone>>, lengths: Read
|
|
|
494
536
|
counts.unmeasured++;
|
|
495
537
|
} else if (isInferred) inferred++;
|
|
496
538
|
else refTilt.push(pictureAngle(rh, lh));
|
|
497
|
-
|
|
498
|
-
const ls = at(i, 'l_shoulder');
|
|
499
|
-
if (rs === null || ls === null) return;
|
|
500
|
-
refSway.push(((rh[0] + lh[0]) / 2 - (rs[0] + ls[0]) / 2) * s);
|
|
501
|
-
refSw.push(Math.hypot(ls[0] - rs[0], ls[1] - rs[1]) * s);
|
|
502
|
-
});
|
|
539
|
+
}
|
|
503
540
|
const bone = pelvis.bones[0]!;
|
|
504
541
|
const aim = pelvis.aim[0]!;
|
|
505
542
|
let tilt: Posture['tilt'] = null;
|
|
@@ -538,6 +575,36 @@ export function measurePosture(posed: Array<Map<string, RawBone>>, lengths: Read
|
|
|
538
575
|
ratio: rig === null ? null : rig.range / reference.range,
|
|
539
576
|
ratioSw: rig === null || rig.rangeSw === null ? null : rig.rangeSw / reference.rangeSw,
|
|
540
577
|
};
|
|
578
|
+
// rig-c/pose#295: a pelvis bone keyed from the track turns the bones it carries about its own origin, and the
|
|
579
|
+
// rig's mid-shoulder with them; the trunk's own sway is the mid-shoulder about the origin it carries.
|
|
580
|
+
if (keyed !== null && rigShoulder !== null && rig !== null) {
|
|
581
|
+
const h = pelvis.bones[0]!;
|
|
582
|
+
const b0 = posed[0]!;
|
|
583
|
+
/** The child of `h` on the way up from `name`, or null when `h` is not above it. */
|
|
584
|
+
const childOf = (name: string): string | null => {
|
|
585
|
+
let at: string | null = name;
|
|
586
|
+
while (at !== null) {
|
|
587
|
+
const up: string | null = b0.get(at)?.parent ?? null;
|
|
588
|
+
if (up === h) return at;
|
|
589
|
+
at = up;
|
|
590
|
+
}
|
|
591
|
+
return null;
|
|
592
|
+
};
|
|
593
|
+
const carriers = pointParts(rigShoulder).map((part) => childOf(partBone(part)));
|
|
594
|
+
if (carriers.some((c) => c !== null)) {
|
|
595
|
+
const carryX = (b: Map<string, RawBone>) => carriers.reduce((sum, c) => sum + (c === null ? 0 : b.get(c)!.worldX - b.get(h)!.worldX), 0) / carriers.length;
|
|
596
|
+
const xs = posed.map((b) => b.get(r.origin!.bone)!.worldX - pointAt(b, lengths, rigShoulder)![0] + carryX(b));
|
|
597
|
+
const trunk = rangeOf(xs);
|
|
598
|
+
sway.carried = {
|
|
599
|
+
bone: h,
|
|
600
|
+
source: keyed.source,
|
|
601
|
+
origins: [...new Set(carriers.filter((c): c is string => c !== null))],
|
|
602
|
+
carry: rangeOf(posed.map(carryX)).range,
|
|
603
|
+
trunk,
|
|
604
|
+
ratio: reference.range > 0 ? trunk.range / reference.range : null,
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
}
|
|
541
608
|
}
|
|
542
609
|
const bounce: BounceSpan[] = [];
|
|
543
610
|
if (r.planted && r.feet !== null && r.feet.supports.length > 0) {
|
|
@@ -648,6 +715,7 @@ export function measurePosture(posed: Array<Map<string, RawBone>>, lengths: Read
|
|
|
648
715
|
frames: { tilt: refTilt.length, sway: refSway.length, of: frames.length },
|
|
649
716
|
tilt,
|
|
650
717
|
...(tilt === null ? { tiltWhy: why } : {}),
|
|
718
|
+
...(keyed !== null ? { tiltTrack: { source: keyed.source, frames: onTrack, declined } } : {}),
|
|
651
719
|
...(inferred > 0 ? { hipsInferred: { frames: inferred, source: inferredSource } } : {}),
|
|
652
720
|
...(measured !== null
|
|
653
721
|
? {
|
|
@@ -696,10 +764,18 @@ export function postureLines(q: Posture): string[] {
|
|
|
696
764
|
: '';
|
|
697
765
|
const measured = m === undefined ? '' : `; tilt measured (${m.method}) on ${m.frames} frame(s): ${m.read} read, ${m.interpolated} interpolated${scaledPart}, ${m.held} held (held: ${heldSpans})${unmeasuredPart}`;
|
|
698
766
|
const left = q.hipsInferred === undefined ? '' : `; ${q.hipsInferred.frames} frame(s) with an inferred hip not read (${q.hipsInferred.source})`;
|
|
767
|
+
// rig-c/pose#295: the reference read off the pelvis track, named as the PELVIS line names it.
|
|
768
|
+
const tr = q.tiltTrack;
|
|
769
|
+
const fromTrack =
|
|
770
|
+
tr === undefined
|
|
771
|
+
? ''
|
|
772
|
+
: tr.declined.length === 0
|
|
773
|
+
? `; all ${q.frames.of} frames from the track`
|
|
774
|
+
: `; ${tr.frames} of ${q.frames.of} frames from the track, the hip line on the ${q.frames.of - tr.frames} it declined (frames ${spansText(tr.declined)})`;
|
|
699
775
|
const out = [
|
|
700
776
|
t === null
|
|
701
777
|
? `hip tilt not read: ${q.tiltWhy}`
|
|
702
|
-
: `hip tilt range (chain "${q.pelvis}", r_hip → l_hip, Spine degrees): reference ${f1(t.reference.range)}
|
|
778
|
+
: `hip tilt range (chain "${q.pelvis}", r_hip → l_hip, Spine degrees): reference ${f1(t.reference.range)}°${tr === undefined ? '' : ` from ${tr.source}`} (${sg(t.reference.min)}° … ${sg(t.reference.max)}°${over(q.frames.tilt)}${fromTrack}), rig ${f1(t.rig.range)}° (${sg(t.rig.min)}° … ${sg(t.rig.max)}°) — rig/reference ${f3(t.ratio)}${measured}${left}`,
|
|
703
779
|
];
|
|
704
780
|
const w = q.sway;
|
|
705
781
|
if (w === null) out.push('sway: the reference has no frame with both hips and both shoulders good; not measured');
|
|
@@ -710,6 +786,11 @@ export function postureLines(q: Posture): string[] {
|
|
|
710
786
|
const sw = w.rig.rangeSw === null ? 'no shoulder width' : `${f3(w.rig.rangeSw)} shoulder widths`;
|
|
711
787
|
const ratioSw = w.ratioSw === null ? '' : `, ${f3(w.ratioSw)} in shoulder widths`;
|
|
712
788
|
out.push(`sway, x-range of mid_hip − mid_shoulder: ${ref}, rig ${f1(w.rig.range)} units (${sw}; mid_hip = "${q.rigHip}", mid_shoulder = "${q.rigShoulder}") — rig/reference ${f3(w.ratio!)} in units${ratioSw}`);
|
|
789
|
+
const c = w.carried;
|
|
790
|
+
if (c !== undefined) {
|
|
791
|
+
const origins = c.origins.map((o) => `"${o}"`).join(', ');
|
|
792
|
+
out.push(`sway: the rig's ${f1(w.rig.range)} units include the pelvis track's turn — bone "${c.bone}" (keyed from ${c.source}) carries ${origins} over ${f1(c.carry)} units of x; the trunk alone (mid_shoulder about that origin) ${f1(c.trunk.range)} units — rig/reference ${c.ratio === null ? 'n/a' : f3(c.ratio)} in units`);
|
|
793
|
+
}
|
|
713
794
|
}
|
|
714
795
|
}
|
|
715
796
|
if (q.bounce !== undefined) {
|