@rig-c/pose 0.39.0 → 0.40.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 +26 -5
- package/cli.ts +31 -5
- package/package.json +1 -1
- package/src/draworder.ts +290 -0
- package/src/reduce.ts +21 -12
- package/src/retarget.ts +82 -3
- package/src/run.ts +88 -17
- package/src/switch.ts +64 -2
package/src/switch.ts
CHANGED
|
@@ -180,8 +180,28 @@ export interface Uncovered {
|
|
|
180
180
|
times: number[];
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
+
/** One run of frames (rig-c/pose#279): first and last frame, how many, and the seconds the minimum span measured it by (first frame's time to the next frame's, the animation's end for the last). */
|
|
184
|
+
export interface FrameRun {
|
|
185
|
+
frames: [number, number];
|
|
186
|
+
count: number;
|
|
187
|
+
seconds: number;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* rig-c/pose#279: per set selected by angle (not `phase`), the runs of frames on which it was the nearest set within
|
|
192
|
+
* the tolerance (inside its `frames` window, before bridging, hysteresis and the minimum span), and the runs the
|
|
193
|
+
* minimum span dropped — shorter than `switchMin` where they stood — on frames the set did not show in the end.
|
|
194
|
+
*/
|
|
195
|
+
export interface SetRuns {
|
|
196
|
+
set: string;
|
|
197
|
+
nearest: FrameRun[];
|
|
198
|
+
suppressed: FrameRun[];
|
|
199
|
+
}
|
|
200
|
+
|
|
183
201
|
export interface Switches {
|
|
184
202
|
switches: Switch[];
|
|
203
|
+
/** rig-c/pose#279: one per set without a `phase`, in the map's order. */
|
|
204
|
+
runs: SetRuns[];
|
|
185
205
|
/** The `attachment` tracks, one per slot whose picture changes. */
|
|
186
206
|
slotTracks: SlotTrack[];
|
|
187
207
|
/** The offsets' `translate` tracks, one per bone a shown set offsets. */
|
|
@@ -490,6 +510,11 @@ export function selectSwitches(map: JointMap, targets: ReadonlyMap<string, reado
|
|
|
490
510
|
const distances = setDistances(map, targets, n, opts.shortInfo);
|
|
491
511
|
const hyst = opts.switchHysteresis ?? SWITCH_LEAVE_HYSTERESIS;
|
|
492
512
|
const switches: Switch[] = [];
|
|
513
|
+
// rig-c/pose#279: per angle set, the frames it was nearest on and the frames the minimum span dropped it from.
|
|
514
|
+
const nearestOn = new Map<string, boolean[]>();
|
|
515
|
+
const droppedOn = new Map<string, boolean[]>();
|
|
516
|
+
const runOf = (i: number, j: number): FrameRun => ({ frames: [i, j], count: j - i + 1, seconds: r3((j + 1 < n ? times[j + 1]! : duration) - times[i]!) });
|
|
517
|
+
const runsOfFlags = (flag: readonly boolean[]): FrameRun[] => runsOf(flag, 0).map(([i, j]) => runOf(i, j));
|
|
493
518
|
// rig-c/pose#286: the supporting side on each frame, from the report's support spans (null: no foot, or no set declares a phase).
|
|
494
519
|
const supportAt: Array<Side | null> = Array.from({ length: n }, () => null);
|
|
495
520
|
for (const sp of opts.phaseSupports ?? []) for (let i = sp.frames[0]; i <= Math.min(sp.frames[1], n - 1); i++) supportAt[i] = sp.side;
|
|
@@ -546,6 +571,10 @@ export function selectSwitches(map: JointMap, targets: ReadonlyMap<string, reado
|
|
|
546
571
|
}
|
|
547
572
|
pick.push(best);
|
|
548
573
|
}
|
|
574
|
+
for (const a of group) {
|
|
575
|
+
nearestOn.set(a.name, pick.map((p) => p === a));
|
|
576
|
+
droppedOn.set(a.name, Array.from({ length: n }, () => false));
|
|
577
|
+
}
|
|
549
578
|
const bridgeSame = (arr: Array<AlternateSet | null>) => {
|
|
550
579
|
for (let i = 0; i < n;) {
|
|
551
580
|
const a = arr[i];
|
|
@@ -604,7 +633,13 @@ export function selectSwitches(map: JointMap, targets: ReadonlyMap<string, reado
|
|
|
604
633
|
let j = i;
|
|
605
634
|
while (j + 1 < n && pick[j + 1] === a) j++;
|
|
606
635
|
const to = j + 1 < n ? times[j + 1]! : duration;
|
|
607
|
-
if (a !== null && a !== undefined && to - times[i]! < opts.switchMin - 1e-9)
|
|
636
|
+
if (a !== null && a !== undefined && to - times[i]! < opts.switchMin - 1e-9) {
|
|
637
|
+
const dropped = droppedOn.get(a.name)!;
|
|
638
|
+
for (let k = i; k <= j; k++) {
|
|
639
|
+
pick[k] = null;
|
|
640
|
+
dropped[k] = true;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
608
643
|
i = j + 1;
|
|
609
644
|
}
|
|
610
645
|
if (hyst > 0) {
|
|
@@ -658,8 +693,14 @@ export function selectSwitches(map: JointMap, targets: ReadonlyMap<string, reado
|
|
|
658
693
|
}
|
|
659
694
|
if (dropped) bridgeSame(pick);
|
|
660
695
|
}
|
|
696
|
+
// rig-c/pose#279: a dropped frame the set came back to (hysteresis, bridging) was not suppressed.
|
|
697
|
+
for (const a of group) {
|
|
698
|
+
const dropped = droppedOn.get(a.name)!;
|
|
699
|
+
for (let k = 0; k < n; k++) if (pick[k] === a) dropped[k] = false;
|
|
700
|
+
}
|
|
661
701
|
spans(pick, opts);
|
|
662
702
|
}
|
|
703
|
+
const runs: SetRuns[] = map.alternates.filter((a) => nearestOn.has(a.name)).map((a) => ({ set: a.name, nearest: runsOfFlags(nearestOn.get(a.name)!), suppressed: runsOfFlags(droppedOn.get(a.name)!) }));
|
|
663
704
|
const order = (name: string) => map.alternates.findIndex((a) => a.name === name);
|
|
664
705
|
switches.sort((x, y) => x.frames[0] - y.frames[0] || order(x.set) - order(y.set));
|
|
665
706
|
|
|
@@ -1094,7 +1135,7 @@ export function selectSwitches(map: JointMap, targets: ReadonlyMap<string, reado
|
|
|
1094
1135
|
for (let k = 0; k + 1 < keys.length; k++) keys[k]!.ease = 'stepped';
|
|
1095
1136
|
offsetTracks.push({ bone: b, property: 'translate', keys });
|
|
1096
1137
|
}
|
|
1097
|
-
return { switches, slotTracks, offsetTracks, shown, distances, chainHolds };
|
|
1138
|
+
return { switches, runs, slotTracks, offsetTracks, shown, distances, chainHolds };
|
|
1098
1139
|
}
|
|
1099
1140
|
|
|
1100
1141
|
/** Per chain, the frames on which a switch of a set depicting it shows: its art was painted for that pose. */
|
|
@@ -1281,6 +1322,27 @@ export function phaseLines(map: JointMap, switches: readonly Switch[]): string[]
|
|
|
1281
1322
|
return out;
|
|
1282
1323
|
}
|
|
1283
1324
|
|
|
1325
|
+
/** How many runs a `SWITCH-MIN` line lists before it points at the report for the rest. */
|
|
1326
|
+
export const SWITCH_MIN_LISTED = 4;
|
|
1327
|
+
|
|
1328
|
+
/**
|
|
1329
|
+
* rig-c/pose#279: one `SWITCH-MIN` line per set the minimum span held back — the runs it dropped (frames, count,
|
|
1330
|
+
* seconds), and the set's nearest runs (how many, and the longest) — so a set that matched but was held back reads
|
|
1331
|
+
* apart from one that never matched. A set with nothing suppressed prints no line.
|
|
1332
|
+
*/
|
|
1333
|
+
export function switchMinLines(runs: readonly SetRuns[], switchMin: number): string[] {
|
|
1334
|
+
const runText = (r: FrameRun) => `${r.count === 1 ? `frame ${r.frames[0]}` : `frames ${r.frames[0]}–${r.frames[1]}`} (${r.count} frame${r.count === 1 ? '' : 's'}, ${r.seconds.toFixed(3)}s)`;
|
|
1335
|
+
const out: string[] = [];
|
|
1336
|
+
for (const s of runs) {
|
|
1337
|
+
if (s.suppressed.length === 0) continue;
|
|
1338
|
+
const listed = s.suppressed.slice(0, SWITCH_MIN_LISTED).map(runText).join(', ');
|
|
1339
|
+
const more = s.suppressed.length > SWITCH_MIN_LISTED ? `, and ${s.suppressed.length - SWITCH_MIN_LISTED} more (the report's switchRuns lists every one)` : '';
|
|
1340
|
+
const longest = s.nearest.reduce((m, r) => (r.count > m.count ? r : m), s.nearest[0]!);
|
|
1341
|
+
out.push(`set "${s.set}": ${s.suppressed.length} run(s) shorter than --switch-min ${switchMin}s not shown: ${listed}${more}; nearest on ${s.nearest.length} run(s), the longest ${runText(longest)}`);
|
|
1342
|
+
}
|
|
1343
|
+
return out;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1284
1346
|
const spd = (v: number) => `${v.toFixed(1)}°/frame`;
|
|
1285
1347
|
const win = (w: [number, number]) => (w[0] === w[1] ? `window frame ${w[0]}` : `window frames ${w[0]}–${w[1]}`);
|
|
1286
1348
|
function moveText(m: Move): string {
|