@rig-c/pose 0.30.1 → 0.31.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 +1 -1
- package/package.json +1 -1
- package/src/legs.ts +69 -18
- package/src/retarget.ts +27 -7
- package/src/run.ts +14 -3
package/README.md
CHANGED
|
@@ -200,7 +200,7 @@ Re-checked on a @rig-c/parts fixture rig with the short hold, the flip guard and
|
|
|
200
200
|
|
|
201
201
|
A knee bending toward the camera is seen from the front only as a shorter leg, and a rig with rigid legs cannot lower its hip without lifting its feet ([#47](https://github.com/rig-c/pose/issues/47)). Two flags, both off by default:
|
|
202
202
|
|
|
203
|
-
**`--leg-scale <min,max>`** scales each mapped leg segment (two of one side's hip, knee and ankle) along its own x axis — a `scalex` key — by its picture length on the frame over its **straight length**, the larger of the two sides' 95th-percentile picture lengths for that segment over the sequence ([#148](https://github.com/rig-c/pose/issues/148); on a non-`in-place` sequence longer than 25 frames, each frame's segment length is normalised by a continuous rate-limited figure-scale signal `figureScaleOf` derived from posture-invariant measures — `max(r_thigh + r_shin, l_thigh + l_shin)
|
|
203
|
+
**`--leg-scale <min,max>`** scales each mapped leg segment (two of one side's hip, knee and ankle) along its own x axis — a `scalex` key — by its picture length on the frame over its **straight length**, the larger of the two sides' 95th-percentile picture lengths for that segment over the sequence ([#148](https://github.com/rig-c/pose/issues/148), weighted by the segment's share of the two legs' mean setup bone length when the rig's setup legs differ by more than 0.1 % so an asymmetrically drawn rig places both ankles at the sequence's relative height, [#225](https://github.com/rig-c/pose/issues/225); on a non-`in-place` sequence longer than 25 frames, each frame's segment length is normalised by a continuous rate-limited figure-scale signal `figureScaleOf` derived from posture- and crouch-invariant segment measures — `max(r_thigh + r_shin, l_thigh + l_shin)` plus the `mid_hip → mid_shoulder` trunk segment held at each support span's 95th-percentile straight length when the 5-series consensus moves past ±2 %, never vertical `ankle.y − nose.y` — with frame-to-frame step ≤ 1 % and frozen spans ≥ 5 frames held at their median, so neither camera zoom, a forward trunk bend nor a landing crouch reads as the figure changing size, and `--camera <file>` writes that track as `rig-pose-camera/1` without changing `motion.json`, [#159](https://github.com/rig-c/pose/issues/159), [#161](https://github.com/rig-c/pose/issues/161), [#163](https://github.com/rig-c/pose/issues/163), [#172](https://github.com/rig-c/pose/issues/172), [#226](https://github.com/rig-c/pose/issues/226)), clamped to [min, max]. When the camera-normalised trunk (`neck → mid_hip` or `mid_shoulder → mid_hip`) foreshortens or extends by more than 4 % from its standing length, the trunk bone is keyed in `scaley` (for a `+y`-aimed trunk, `scalex` for `+x`) clamped to the same `[min, max]` bounds, carrying the trunk and head's depth bend while keeping the aimed rotation ([#172](https://github.com/rig-c/pose/issues/172)). The bounds are `<min,max>` with `0 < min ≤ 1 ≤ max`. A segment the map aims other than `"x"` is refused (`LEGS_MAP`). `LEG-SCALE` lists each segment's frames clamped at either limit with the unclamped `asked` scale range on each clamped run (`legScale[].clamped` in `--report`, `{ frames, times, limit, asked: { min, max } }`, [#225](https://github.com/rig-c/pose/issues/225)).
|
|
204
204
|
|
|
205
205
|
rig-c's `scalex` multiplies the bone's x axis, and a child with the `normal` inherit mode takes its parent's whole world matrix. So under a thigh scaled by s, a shin at angle φ in the thigh's axes points at atan2(sin φ, s·cos φ), and is itself scaled by |(s·cos φ, sin φ)|. A child (or its intermediate control bone) with `noScale` or `noScaleOrReflection` ([#153](https://github.com/rig-c/pose/issues/153)) still has its direction carried through the parent's scaled matrix, so its angle is skewed the same way, while rig-c normalises that direction to unit length so the parent's scale is not inherited. The rotate key `turn − turn above` assumes unit scale. A mapped bone under a scaled bone is therefore keyed through its parent's posed matrix: a `rotate` key k and a `scalex` key s' put its world x axis on its target at the scale asked (under `noScale`, s' is the bone's own asked scale s directly). On `dance-a` the unit-scale formula missed the shins by up to 2.4°, their length by 0.16 of setup and the ankles by 38 units; solved, every bone lands. A `normal` child's art is still sheared by its parent's scale, and a bone under it (a foot) is squashed along the shin: `SHEAR` reads both.
|
|
206
206
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rig-c/pose",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "Puts motion from a body-18 pose sequence onto a rig built with rig-c: bone keys in rig-c's motion format, checked through rig-c's gate before anything is written. A CLI for agents that cannot see the rig.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/legs.ts
CHANGED
|
@@ -295,11 +295,18 @@ export interface SupportScaleRun {
|
|
|
295
295
|
clamped?: { bound: number; hipRise: number };
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
+
export interface ClampedScaleRun {
|
|
299
|
+
frames: [number, number];
|
|
300
|
+
limit: 'low' | 'high';
|
|
301
|
+
/** The lowest and highest scale asked (`picture length ÷ straight`) on the clamped run's frames (#225). */
|
|
302
|
+
asked: { min: number; max: number };
|
|
303
|
+
}
|
|
304
|
+
|
|
298
305
|
export interface LegScale {
|
|
299
306
|
chain: string;
|
|
300
307
|
bone: string;
|
|
301
308
|
joints: [string, string];
|
|
302
|
-
/** The straight length: the larger of the two sides' 95th-percentile picture lengths for this segment (#148), px. */
|
|
309
|
+
/** The straight length: the larger of the two sides' 95th-percentile picture lengths for this segment (#148), weighted by the bone's setup length share when the rig's legs differ at setup (#225), px. */
|
|
303
310
|
straight: number;
|
|
304
311
|
/** The picture length over the straight length, before the clamp: lowest, median, highest. */
|
|
305
312
|
ratio: { min: number; median: number; max: number };
|
|
@@ -308,6 +315,8 @@ export interface LegScale {
|
|
|
308
315
|
/** Runs of frames clamped at the low and at the high limit. */
|
|
309
316
|
low: Array<[number, number]>;
|
|
310
317
|
high: Array<[number, number]>;
|
|
318
|
+
/** Runs of frames clamped at the low or high limit, with the asked scale range on each run (#225). */
|
|
319
|
+
clamped?: ClampedScaleRun[];
|
|
311
320
|
/** The scale keyed on each frame (clamped). */
|
|
312
321
|
scale: number[];
|
|
313
322
|
/** The scale the pose asked for on each frame before the clamp floor (`min(ratio, limits[1])`, rig-c/pose#107). */
|
|
@@ -321,7 +330,7 @@ export interface LegScale {
|
|
|
321
330
|
support?: SupportScaleRun[];
|
|
322
331
|
}
|
|
323
332
|
|
|
324
|
-
/** One segment's scale per frame: picture length ÷ straight length (the larger of the two sides' 95th percentiles, #148), clamped; its asked scale without the clamp floor (#107); and the runs clamped. */
|
|
333
|
+
/** One segment's scale per frame: picture length ÷ straight length (the larger of the two sides' 95th percentiles, #148, scaled per leg when setup leg lengths differ, #225), clamped; its asked scale without the clamp floor (#107); and the runs clamped with their asked values (#225). */
|
|
325
334
|
export function legScaleOf(lengths: readonly number[], limits: [number, number], straight = percentile(lengths, STRAIGHT_PERCENTILE)): Omit<LegScale, 'chain' | 'bone' | 'joints'> {
|
|
326
335
|
const ratio = lengths.map((l) => l / straight);
|
|
327
336
|
const scale = ratio.map((r) => Math.min(Math.max(r, limits[0]), limits[1]));
|
|
@@ -329,12 +338,23 @@ export function legScaleOf(lengths: readonly number[], limits: [number, number],
|
|
|
329
338
|
const sorted = [...ratio].sort((a, b) => a - b);
|
|
330
339
|
const mid = sorted.length >> 1;
|
|
331
340
|
const median = sorted.length % 2 === 1 ? sorted[mid]! : (sorted[mid - 1]! + sorted[mid]!) / 2;
|
|
341
|
+
const low = runs(ratio.map((r) => r < limits[0]));
|
|
342
|
+
const high = runs(ratio.map((r) => r > limits[1]));
|
|
343
|
+
const spanAsked = ([a, b]: [number, number]): { min: number; max: number } => {
|
|
344
|
+
const sub = ratio.slice(a, b + 1);
|
|
345
|
+
return { min: Math.min(...sub), max: Math.max(...sub) };
|
|
346
|
+
};
|
|
347
|
+
const clamped: ClampedScaleRun[] = [
|
|
348
|
+
...low.map((frames) => ({ frames, limit: 'low' as const, asked: spanAsked(frames) })),
|
|
349
|
+
...high.map((frames) => ({ frames, limit: 'high' as const, asked: spanAsked(frames) })),
|
|
350
|
+
];
|
|
332
351
|
return {
|
|
333
352
|
straight,
|
|
334
353
|
ratio: { min: sorted[0]!, median, max: sorted[sorted.length - 1]! },
|
|
335
354
|
limits,
|
|
336
|
-
low
|
|
337
|
-
high
|
|
355
|
+
low,
|
|
356
|
+
high,
|
|
357
|
+
...(clamped.length > 0 ? { clamped } : {}),
|
|
338
358
|
scale,
|
|
339
359
|
asked,
|
|
340
360
|
};
|
|
@@ -671,7 +691,7 @@ export function plantHip(feet: Feet, offset: (i: number, s: Side) => Pt, travel:
|
|
|
671
691
|
const y = floor ?? percentile(heights as number[], 0.5);
|
|
672
692
|
const shifts = feet.supports.map((sp, k) => ({ side: sp.side, frames: [sp.frames[0], sp.frames[1]] as [number, number], shift: y - heights[k]! }));
|
|
673
693
|
const placed = placeHip(feet, offset, travel, start, y, follow);
|
|
674
|
-
const closure = follow === undefined ? null : closeLoop(placed.hip);
|
|
694
|
+
const closure = follow === undefined ? null : closeLoop(placed.hip, feet.supports);
|
|
675
695
|
return { hip: placed.hip, floor: { y, given: floor !== undefined, shifts }, blends: placed.blends, closure };
|
|
676
696
|
} finally {
|
|
677
697
|
trackFn = undefined;
|
|
@@ -685,15 +705,35 @@ export interface Closure {
|
|
|
685
705
|
}
|
|
686
706
|
|
|
687
707
|
/**
|
|
688
|
-
* Close an in-place loop (rig-c/pose#92):
|
|
689
|
-
*
|
|
690
|
-
*
|
|
691
|
-
*
|
|
708
|
+
* Close an in-place loop (rig-c/pose#92, #224): when support spans repeat across
|
|
709
|
+
* multiple cycles, measure the per-cycle x drift between matching phase frames
|
|
710
|
+
* (`cf = 0` of cycle 1 vs cycle `k + 1`) and subtract that linear trend so the
|
|
711
|
+
* within-cycle offset (`cf 0 → last frame of cycle`) is preserved; on a single
|
|
712
|
+
* cycle (or when no repeating support spans are given), subtract the whole-clip
|
|
713
|
+
* displacement `hip[n - 1].x - hip[0].x`. The y is the floor's (#85) and is not
|
|
714
|
+
* touched. In place.
|
|
692
715
|
*/
|
|
693
|
-
export function closeLoop(hip: Pt[]): Closure {
|
|
716
|
+
export function closeLoop(hip: Pt[], supports?: Feet['supports']): Closure {
|
|
694
717
|
const n = hip.length;
|
|
695
|
-
|
|
696
|
-
|
|
718
|
+
if (n <= 1) return { drift: 0, frames: n };
|
|
719
|
+
let perFrame = (hip[n - 1]![0] - hip[0]![0]) / (n - 1);
|
|
720
|
+
if (supports !== undefined && supports.length > 0) {
|
|
721
|
+
const bySide: Record<Side, number[]> = { r: [], l: [] };
|
|
722
|
+
for (const sp of supports) {
|
|
723
|
+
if (sp.frames[0] > 0) bySide[sp.side].push(sp.frames[0]);
|
|
724
|
+
}
|
|
725
|
+
const starts = bySide.r.length >= bySide.l.length ? bySide.r : bySide.l;
|
|
726
|
+
if (starts.length >= 2) {
|
|
727
|
+
const period = Math.round((starts[starts.length - 1]! - starts[0]!) / (starts.length - 1));
|
|
728
|
+
const cycles = period > 0 ? Math.floor((n - 1) / period) : 0;
|
|
729
|
+
if (cycles >= 1 && cycles * period < n) {
|
|
730
|
+
const span = cycles * period;
|
|
731
|
+
perFrame = (hip[span]![0] - hip[0]![0]) / span;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
const drift = perFrame * (n - 1);
|
|
736
|
+
for (let i = 0; i < n; i++) hip[i]![0] -= perFrame * i;
|
|
697
737
|
return { drift, frames: n };
|
|
698
738
|
}
|
|
699
739
|
|
|
@@ -739,7 +779,7 @@ function placeHip(feet: Feet, offset: (i: number, s: Side) => Pt, travel: (i: nu
|
|
|
739
779
|
const b = travel(mode.from);
|
|
740
780
|
let y = mode.at[1] + a[1] - b[1];
|
|
741
781
|
if (mode.carry !== undefined && mode.land !== undefined && mode.end !== undefined && floorY !== null) {
|
|
742
|
-
if (mode.land < n && mode.land > mode.from) {
|
|
782
|
+
if ((mode.land < n || follow !== undefined) && mode.land > mode.from) {
|
|
743
783
|
y += mode.carry * ((Math.min(i, mode.land) - mode.from) / (mode.land - mode.from));
|
|
744
784
|
}
|
|
745
785
|
if (i <= mode.end) {
|
|
@@ -748,7 +788,7 @@ function placeHip(feet: Feet, offset: (i: number, s: Side) => Pt, travel: (i: nu
|
|
|
748
788
|
}
|
|
749
789
|
return [mode.at[0] + a[0] - b[0], y];
|
|
750
790
|
};
|
|
751
|
-
/** A travel span from `startFrame` off frame `from`'s hip `at`: carried to meet `floorY` at landing when it contains a jump (#103). */
|
|
791
|
+
/** A travel span from `startFrame` off frame `from`'s hip `at`: carried to meet `floorY` at landing when it contains a jump (#103, and wrapped to frame 0 on an in-place loop, #224). */
|
|
752
792
|
const makeTravel = (startFrame: number, from: number, at: Pt): Mode => {
|
|
753
793
|
let end = startFrame;
|
|
754
794
|
while (end + 1 < n && (feet.support[end + 1] ?? null) === null && (tracked[end + 1] ?? null) === null) end++;
|
|
@@ -756,10 +796,11 @@ function placeHip(feet: Feet, offset: (i: number, s: Side) => Pt, travel: (i: nu
|
|
|
756
796
|
if (!isJump) return { kind: 'travel', from, at };
|
|
757
797
|
const land = end + 1;
|
|
758
798
|
let carry = 0;
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
const
|
|
799
|
+
const landIdx = land < n ? land : follow !== undefined ? 0 : -1;
|
|
800
|
+
const landSide = landIdx >= 0 ? (feet.support[landIdx] ?? tracked[landIdx] ?? null) : null;
|
|
801
|
+
if (landSide !== null) {
|
|
802
|
+
const rawLandY = at[1] + travel(landIdx)[1] - travel(from)[1];
|
|
803
|
+
const targetLandY = floorY - offset(landIdx, landSide)[1];
|
|
763
804
|
carry = targetLandY - rawLandY;
|
|
764
805
|
}
|
|
765
806
|
return { kind: 'travel', from, at, end, land, carry };
|
|
@@ -780,7 +821,17 @@ function placeHip(feet: Feet, offset: (i: number, s: Side) => Pt, travel: (i: nu
|
|
|
780
821
|
if (i === 0) {
|
|
781
822
|
if (s !== null) {
|
|
782
823
|
const o = offset(0, s);
|
|
824
|
+
const anchorY = floorY ?? (start[1] + o[1]);
|
|
783
825
|
mode = plant(0, s, [start[0] + o[0], start[1] + o[1]]);
|
|
826
|
+
// #224: in a periodic in-place loop whose last frame is in flight, the first cycle's plant blends from that flight just like cycles 2..N.
|
|
827
|
+
if (follow !== undefined && n > 1 && (feet.support[n - 1] ?? null) === null && (tracked[n - 1] ?? null) === null) {
|
|
828
|
+
from = { kind: 'travel', from: 0, at: [start[0], anchorY - o[1]] };
|
|
829
|
+
blendAt = 0;
|
|
830
|
+
blendEnd = 0;
|
|
831
|
+
while (blendEnd + 1 < n && (feet.support[blendEnd + 1] ?? null) === s) blendEnd++;
|
|
832
|
+
blendToFlight =
|
|
833
|
+
blendEnd + 1 < n && (feet.support[blendEnd + 1] ?? null) === null && (tracked[blendEnd + 1] ?? null) === null;
|
|
834
|
+
}
|
|
784
835
|
} else if (tr !== null) {
|
|
785
836
|
const o = offset(0, tr);
|
|
786
837
|
mode = { kind: 'track', side: tr, anchor: [start[0] + o[0], floorY ?? (start[1] + o[1])], from: 0, at: start };
|
package/src/retarget.ts
CHANGED
|
@@ -839,13 +839,17 @@ export function retarget(rest: Rest, map: JointMap, frames: ReadonlyArray<Partia
|
|
|
839
839
|
return cons.map((c) => c * legSum[0]!);
|
|
840
840
|
}
|
|
841
841
|
}
|
|
842
|
-
const
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
842
|
+
const segH =
|
|
843
|
+
rs !== null && ls !== null
|
|
844
|
+
? legSum.map((l, i) => l + dist2([(hr[i]![0] + hl[i]![0]) / 2, (hr[i]![1] + hl[i]![1]) / 2], [(rs[i]![0] + ls[i]![0]) / 2, (rs[i]![1] + ls[i]![1]) / 2]))
|
|
845
|
+
: legSum;
|
|
846
|
+
const out = winPercentile(segH, 15, STRAIGHT_PERCENTILE);
|
|
847
|
+
if (feet !== null) {
|
|
848
|
+
for (const sp of feet.supports) {
|
|
849
|
+
const m = percentile(segH.slice(sp.frames[0], sp.frames[1] + 1), STRAIGHT_PERCENTILE);
|
|
850
|
+
for (let i = sp.frames[0]; i <= sp.frames[1]; i++) out[i] = m;
|
|
851
|
+
}
|
|
846
852
|
}
|
|
847
|
-
const out = winPercentile(legSum, 1, STRAIGHT_PERCENTILE);
|
|
848
|
-
out[0] = legSum[0]!;
|
|
849
853
|
return out;
|
|
850
854
|
}
|
|
851
855
|
return legSum;
|
|
@@ -937,8 +941,24 @@ export function retarget(rest: Rest, map: JointMap, frames: ReadonlyArray<Partia
|
|
|
937
941
|
}
|
|
938
942
|
}
|
|
939
943
|
}
|
|
944
|
+
const rigLenByKind = new Map<string, number[]>();
|
|
945
|
+
for (const seg of rawSegments) {
|
|
946
|
+
const len = rest.get(seg.bone)?.length ?? 0;
|
|
947
|
+
if (len > 1e-6) {
|
|
948
|
+
const list = rigLenByKind.get(seg.kind) ?? [];
|
|
949
|
+
list.push(len);
|
|
950
|
+
rigLenByKind.set(seg.kind, list);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
const rigShareOf = (seg: (typeof rawSegments)[number]): number => {
|
|
954
|
+
const lens = rigLenByKind.get(seg.kind);
|
|
955
|
+
const rigLen = rest.get(seg.bone)?.length ?? 0;
|
|
956
|
+
if (lens === undefined || lens.length < 2 || !(rigLen > 1e-6)) return 1;
|
|
957
|
+
const meanRigLen = lens.reduce((a, b) => a + b, 0) / lens.length;
|
|
958
|
+
return Math.abs(rigLen - meanRigLen) > 1e-3 * meanRigLen ? rigLen / meanRigLen : 1;
|
|
959
|
+
};
|
|
940
960
|
for (const seg of rawSegments) {
|
|
941
|
-
const ls = legScaleOf(seg.lengths, limits, straightByKind.get(seg.kind)!);
|
|
961
|
+
const ls = legScaleOf(seg.lengths, limits, straightByKind.get(seg.kind)! * rigShareOf(seg));
|
|
942
962
|
legScale.push({ chain: seg.chain, bone: seg.bone, joints: seg.joints, ...ls });
|
|
943
963
|
scales.set(seg.bone, ls.scale);
|
|
944
964
|
}
|
package/src/run.ts
CHANGED
|
@@ -773,12 +773,23 @@ function legRangeLine(which: 'free' | 'supporting', range: Partial<Record<'r' |
|
|
|
773
773
|
return `bound from the leg's own range: on a frame where the picture folds the ${which} leg below ${bounds[0]} of its straight length (hip → ankle, its 95th percentile), the lower bound is that fold — lowest ${sides.join(', ')}; the upper stays ${bounds[1]}`;
|
|
774
774
|
}
|
|
775
775
|
|
|
776
|
-
/** A LEG-SCALE line: the segment, its straight length, its ratio's spread, and the frames clamped. */
|
|
776
|
+
/** A LEG-SCALE line: the segment, its straight length, its ratio's spread, and the frames clamped (with the asked value, #225). */
|
|
777
777
|
function legScaleLine(l: LegScale, times: readonly number[]): string {
|
|
778
778
|
const span = (x: [number, number]) => (x[0] === x[1] ? `${x[0]}` : `${x[0]}–${x[1]}`);
|
|
779
779
|
const count = (rs: Array<[number, number]>) => rs.reduce((k, x) => k + x[1] - x[0] + 1, 0);
|
|
780
|
-
const
|
|
781
|
-
|
|
780
|
+
const val = (r: { min: number; max: number }) => (r.min.toFixed(3) === r.max.toFixed(3) ? r.min.toFixed(3) : `${r.min.toFixed(3)}–${r.max.toFixed(3)}`);
|
|
781
|
+
const askedOf = (x: [number, number], kind: 'low' | 'high'): string => {
|
|
782
|
+
const c = l.clamped?.find((r) => r.limit === kind && r.frames[0] === x[0] && r.frames[1] === x[1]);
|
|
783
|
+
if (c !== undefined) return `asked ${val(c.asked)}`;
|
|
784
|
+
const sub = l.asked.slice(x[0], x[1] + 1);
|
|
785
|
+
return sub.length === 0 ? '' : `asked ${val({ min: Math.min(...sub), max: Math.max(...sub) })}`;
|
|
786
|
+
};
|
|
787
|
+
const clamp = (rs: Array<[number, number]>, at: number, how: string, kind: 'low' | 'high') => {
|
|
788
|
+
if (rs.length === 0) return `none ${how} ${at}`;
|
|
789
|
+
const detail = rs.length === 1 ? ` (${askedOf(rs[0]!, kind)})` : ` (${rs.map((x) => `${span(x)}: ${askedOf(x, kind)}`).join(', ')})`;
|
|
790
|
+
return `${count(rs)} frame(s) ${how} ${at}, clamped (frames ${rs.map(span).join(', ')})${detail}`;
|
|
791
|
+
};
|
|
792
|
+
return `chain "${l.chain}" bone "${l.bone}" (${l.joints[0]} → ${l.joints[1]}): straight length ${l.straight.toFixed(1)}px (larger 95th percentile of ${times.length} frames, both legs); picture length ÷ straight ${l.ratio.min.toFixed(3)} … ${l.ratio.max.toFixed(3)} (median ${l.ratio.median.toFixed(3)}); scalex keyed in [${l.limits[0]}, ${l.limits[1]}]: ${clamp(l.low, l.limits[0], 'under', 'low')}; ${clamp(l.high, l.limits[1], 'over', 'high')}`;
|
|
782
793
|
}
|
|
783
794
|
|
|
784
795
|
/** A LEG-SCALE past the floor (support) line (rig-c/pose#133): the supporting leg segment scaled below `--leg-scale`'s floor down to #95's bound. */
|