@rig-c/pose 0.41.0 → 0.43.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 +21 -11
- package/cli.ts +25 -8
- package/package.json +1 -1
- package/src/clean.ts +5 -4
- package/src/contacts.ts +250 -0
- package/src/depth.ts +53 -13
- package/src/draworder.ts +74 -23
- package/src/legs.ts +7 -0
- package/src/pelvis.ts +263 -4
- package/src/pose.ts +4 -3
- package/src/retarget.ts +158 -14
- package/src/run.ts +26 -9
- package/src/switch.ts +11 -4
package/src/run.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { tmpdir } from 'node:os';
|
|
|
10
10
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
11
11
|
import { knownSetsOf, readMap, resolveMap, type JointMap, type Rest } from './map.ts';
|
|
12
12
|
import { cleanLine, cleanSummaryLine, inferredSource } from './clean.ts';
|
|
13
|
-
import { depthLine, layersOf, type FrameLayers } from './depth.ts';
|
|
13
|
+
import { depthLine, depthRecleanText, forwardShinFrames, layersOf, type FrameLayers } from './depth.ts';
|
|
14
14
|
import { drawGapLines, drawOrderFromDepth, drawOrderKeys, drawOrderLines, type DrawOrderReading } from './draworder.ts';
|
|
15
15
|
import { convertCoco17, personTrack, readPoseSequence, TRAVEL_IN_PLACE, type CameraFrame } from './pose.ts';
|
|
16
16
|
import { isObject, lineOf, parseJson, Problems, Refusal, type Problem } from './problems.ts';
|
|
@@ -27,6 +27,7 @@ import { reportRenderDomain, writeReport, type ReportInput } from './report.ts';
|
|
|
27
27
|
import { PROVENANCE_FILE, provenanceText } from './provenance.ts';
|
|
28
28
|
import { madeLine, planOutDirs } from './outdir.ts';
|
|
29
29
|
import { bodyLine, faceLine } from './face.ts';
|
|
30
|
+
import { contactKeysLines, readContactKeys } from './contacts.ts';
|
|
30
31
|
|
|
31
32
|
export interface RetargetArgs extends RetargetOptions {
|
|
32
33
|
rig: string;
|
|
@@ -81,6 +82,8 @@ export interface RetargetArgs extends RetargetOptions {
|
|
|
81
82
|
output?: 'default' | 'verbose' | 'summary';
|
|
82
83
|
/** `--draw-order depth` (rig-c/pose#304): key the animation's draw order from the sequence's depth. Absent: no draw-order key, the output as before. */
|
|
83
84
|
drawOrder?: 'depth';
|
|
85
|
+
/** `--contact-keys` (rig-c/pose#303): the hand contact keys' file, `rig-pose-contact-keys/1`. Absent: the support as read, the output as before. */
|
|
86
|
+
contactKeysFile?: string;
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
export interface RunResult {
|
|
@@ -104,7 +107,7 @@ export function runRetarget(a: RetargetArgs, log: (line: string) => void): RunRe
|
|
|
104
107
|
throw new Refusal(problems);
|
|
105
108
|
};
|
|
106
109
|
const args = new Problems();
|
|
107
|
-
for (const [flag, path] of [['--map', a.map], ['--pose', a.pose], ['--merge', a.merge]] as const) {
|
|
110
|
+
for (const [flag, path] of [['--map', a.map], ['--pose', a.pose], ['--merge', a.merge], ['--contact-keys', a.contactKeysFile]] as const) {
|
|
108
111
|
if (path !== undefined && !existsSync(path)) args.add('ARG_PATH', `${flag} ${path} does not exist`);
|
|
109
112
|
}
|
|
110
113
|
// rig-c/pose#239: the report is written unless asked elsewhere — beside the motion, as <out>.report.json.
|
|
@@ -141,6 +144,15 @@ export function runRetarget(a: RetargetArgs, log: (line: string) => void): RunRe
|
|
|
141
144
|
// #304: the draw order is read off the sequence's depth; asked for on a sequence without it, refused by name.
|
|
142
145
|
if (a.drawOrder === 'depth' && !seq.frames.some((x) => x.depth !== undefined)) refuse([{ code: 'DRAWORDER_NO_DEPTH', text: `--draw-order depth keys the draw order from the sequence's depth, and ${a.pose} carries none (rig-pose-sequence/${seq.cleaned!.params.depth === undefined ? '1 or 1.1, no "depth" on any frame' : '1.2 with no frame naming a segment'}); clean it with --depth walk|run, or retarget without --draw-order` }]);
|
|
143
146
|
const frames = personTrack(seq, a.person, a.pose);
|
|
147
|
+
// #336: a derived depth whose shins lean the way the trunk faces was cleaned with the table before #336 (a walk's lifted
|
|
148
|
+
// foot forward); the draw order now reads a walk at the ankle, and would draw that foot in front. Refused by name.
|
|
149
|
+
if (a.drawOrder === 'depth') {
|
|
150
|
+
const old = forwardShinFrames(frames, seq.frames);
|
|
151
|
+
if (old.length > 0) refuse([{ code: 'DEPTH_RECLEAN', text: depthRecleanText(a.pose, seq.cleaned!.params.depth?.gait ?? null, old) }]);
|
|
152
|
+
}
|
|
153
|
+
// rig-c/pose#303: the contact keys, read against the sequence's frames before any gate runs.
|
|
154
|
+
const contactKeysSnapshot = a.contactKeysFile === undefined ? null : readFileSync(a.contactKeysFile);
|
|
155
|
+
const contactKeys = contactKeysSnapshot === null ? null : readContactKeys(contactKeysSnapshot.toString('utf8'), a.contactKeysFile!, seq.frames.length);
|
|
144
156
|
const times = seq.frames.map((f) => f.t);
|
|
145
157
|
|
|
146
158
|
const scratch = mkdtempSync(join(tmpdir(), 'rig-pose-'));
|
|
@@ -156,7 +168,7 @@ export function runRetarget(a: RetargetArgs, log: (line: string) => void): RunRe
|
|
|
156
168
|
const slots = slotsOf(doc0);
|
|
157
169
|
resolveMap(map, rest, a.map, slots, input.sets);
|
|
158
170
|
|
|
159
|
-
const r = retarget(rest, map, frames, times, { ...a, ...(seq.travel !== undefined ? { travel: seq.travel } : {}), ...(seq.cleaned!.params.loop ? { loop: true } : {}), ...(seq.pelvisSource !== undefined ? { pelvis: { source: seq.pelvisSource, tilt: seq.frames.map((f) => f.pelvisTilt ?? null) } } : {}) }, slots, input.sets);
|
|
171
|
+
const r = retarget(rest, map, frames, times, { ...a, ...(seq.travel !== undefined ? { travel: seq.travel } : {}), ...(seq.cleaned!.params.loop ? { loop: true } : {}), ...(seq.pelvisSource !== undefined ? { pelvis: { source: seq.pelvisSource, tilt: seq.frames.map((f) => f.pelvisTilt ?? null) } } : {}), ...(contactKeys !== null ? { contactKeys } : {}), ...(seq.cleaned!.params.depth !== undefined ? { gait: seq.cleaned!.params.depth.gait } : {}) }, slots, input.sets);
|
|
160
172
|
if (fragment.length > 0 && ((r.face?.sets?.length ?? 0) > 0 || (r.body?.sets?.length ?? 0) > 0)) {
|
|
161
173
|
const known = knownSetsOf(map, input.sets);
|
|
162
174
|
for (const s of [...(r.face?.sets ?? []), ...(r.body?.sets ?? [])]) {
|
|
@@ -227,8 +239,8 @@ export function runRetarget(a: RetargetArgs, log: (line: string) => void): RunRe
|
|
|
227
239
|
// rig-c/pose#281: the limbs' front-to-back order from the sequence's depth, only when it carries depth.
|
|
228
240
|
const layers: FrameLayers[] | null = seq.frames.some((x) => x.depth !== undefined) ? frames.map((j, i) => layersOf(j, seq.frames[i]!.depth ?? {})) : null;
|
|
229
241
|
if (layers !== null) say(` ${depthLine(layers, seq.frames, seq.cleaned!.params.depth?.gait ?? null)}`);
|
|
230
|
-
// rig-c/pose#304: with --draw-order depth, the legs' draw order keyed from
|
|
231
|
-
const drawOrder: DrawOrderReading | null = a.drawOrder === 'depth' && layers !== null ? drawOrderFromDepth(layers, frames, seq.frames.map((x) => x.depth), r.times, map, rest, doc0.slots, input.sets, seq.cleaned!.params.loop) : null;
|
|
242
|
+
// rig-c/pose#304: with --draw-order depth, the legs' draw order keyed from the depth (#336: at the joint the recorded gait names).
|
|
243
|
+
const drawOrder: DrawOrderReading | null = a.drawOrder === 'depth' && layers !== null ? drawOrderFromDepth(layers, frames, seq.frames.map((x) => x.depth), r.times, map, rest, doc0.slots, input.sets, seq.cleaned!.params.loop, seq.cleaned!.params.depth?.gait ?? null) : null;
|
|
232
244
|
if (drawOrder !== null) for (const l of [...drawOrderLines(drawOrder), ...drawGapLines(drawOrder)]) say(` ${l}`);
|
|
233
245
|
if (r.pivot !== null) say(` PIVOT bone "${r.pivot.bone}"'s origin is ${r.pivot.distance.toFixed(2)} units (${r.pivot.hipWidths.toFixed(3)} hip widths) from its thigh roots' midpoint (${r.pivot.roots.join(' + ')}): dx ${r.pivot.offset[0].toFixed(2)}, dy ${r.pivot.offset[1].toFixed(2)}, at setup — the pivot every hip turn swings the legs about`);
|
|
234
246
|
if (r.feet !== null) {
|
|
@@ -237,7 +249,9 @@ export function runRetarget(a: RetargetArgs, log: (line: string) => void): RunRe
|
|
|
237
249
|
say(` FEET ${r.planted ? 'planted' : 'read, not planted (no --plant-feet)'}: leg length (median hip → ankle) r ${f.legLength.r.toFixed(1)}px, l ${f.legLength.l.toFixed(1)}px; ${f.supports.length} support span(s), ${f.changes.filter((c) => c.from !== null).length} change(s) of support, ${f.jumps.length} jump(s), ${f.steps.length} step(s)`);
|
|
238
250
|
for (const sg of r.segments) say(` FEET ${segmentLine(sg)}`);
|
|
239
251
|
say(` FEET supports: ${f.supports.map((x) => `${x.side} ${span(x.frames)}`).join(', ') || 'none'}`);
|
|
240
|
-
|
|
252
|
+
// rig-c/pose#303: with contact keys, their stride lines stand in for #280's (the keys decide the frames they cover).
|
|
253
|
+
const signal = f.signal === undefined ? [] : signalLines(f.signal, f.supports);
|
|
254
|
+
for (const l of f.contactKeys === undefined ? signal : [...signal.filter((x) => !x.startsWith('stride ')), ...contactKeysLines(f)]) say(` FEET ${l}`);
|
|
241
255
|
say(` FEET jumps (both ankles and mid_hip > 0.05 leg lengths above their rolling floor, ≥ 2 frames): ${f.jumps.map(span).join(', ') || 'none'}`);
|
|
242
256
|
if (f.lowered !== undefined) say(` FEET ${loweredLine(f.lowered)}`);
|
|
243
257
|
if (f.tracked !== undefined) say(` FEET tracked (the lower foot supports on the floor, following its scaled ankle track until still, rig-c/pose#119): ${f.tracked.map((x) => `${x.side} ${span(x.frames)}`).join(', ')}`);
|
|
@@ -423,7 +437,7 @@ export function runRetarget(a: RetargetArgs, log: (line: string) => void): RunRe
|
|
|
423
437
|
|
|
424
438
|
assertReportInputs([rigSnapshot,mapSnapshot,poseSnapshot]);
|
|
425
439
|
// Every retarget option as resolved: the report's `options`, and with the name the build record's `flags` (#270).
|
|
426
|
-
const options = { minScore: a.minScore, flip: a.flip, short: a.short, bends: a.bends, bendTolerance: a.bendTolerance, switchTolerance: a.switchTolerance, switchMin: a.switchMin, switchAt: a.switchAt, profile: a.profile, legScale: a.legScale ?? 'off', plantFeet: a.plantFeet === true, stance: a.stance ?? 'off', floor: a.floor ?? null, raisedByHead: a.raisedByHead === true, idle: idleName, carryIdle: a.carryIdle === true, merge: a.merge ?? null, reduce: a.reduce ?? 'off', ...(a.reduceChains === undefined ? {} : { reduceChains: a.reduceChains }), reduceHip: a.reduceHip ?? 'off', ...(a.reduceKeepPlanted === true ? { reduceKeepPlanted: true } : {}), ...(a.drawOrder !== undefined ? { drawOrder: a.drawOrder } : {}) };
|
|
440
|
+
const options = { minScore: a.minScore, flip: a.flip, short: a.short, bends: a.bends, bendTolerance: a.bendTolerance, switchTolerance: a.switchTolerance, switchMin: a.switchMin, switchAt: a.switchAt, profile: a.profile, legScale: a.legScale ?? 'off', plantFeet: a.plantFeet === true, stance: a.stance ?? 'off', floor: a.floor ?? null, raisedByHead: a.raisedByHead === true, idle: idleName, carryIdle: a.carryIdle === true, merge: a.merge ?? null, reduce: a.reduce ?? 'off', ...(a.reduceChains === undefined ? {} : { reduceChains: a.reduceChains }), reduceHip: a.reduceHip ?? 'off', ...(a.reduceKeepPlanted === true ? { reduceKeepPlanted: true } : {}), ...(a.drawOrder !== undefined ? { drawOrder: a.drawOrder } : {}), ...(a.contactKeysFile !== undefined ? { contactKeys: a.contactKeysFile } : {}) };
|
|
427
441
|
const reportText = writeReport({
|
|
428
442
|
spec: 'rig-pose-report/1',
|
|
429
443
|
rigc,
|
|
@@ -485,8 +499,11 @@ export function runRetarget(a: RetargetArgs, log: (line: string) => void): RunRe
|
|
|
485
499
|
pelvis: r.pelvis,
|
|
486
500
|
// Only when the sequence carries depth (#281): a sequence without it writes the report it always has.
|
|
487
501
|
...(layers !== null ? { layers: layers.map((l, frame) => ({ frame, order: l.order, depth: l.depth })) } : {}),
|
|
488
|
-
// Only with --draw-order depth (#304).
|
|
489
|
-
|
|
502
|
+
// Only with --draw-order depth (#304). A key's `t` here is its frame's time, the report's clock (#214); the
|
|
503
|
+
// motion's key is written half a frame before it (#325).
|
|
504
|
+
...(drawOrder !== null ? { drawOrder: { source: 'depth', pairs: drawOrder.pairs, keys: drawOrder.keys.map((k) => ({ ...k, t: r.times[k.frame]! })), spans: drawOrder.spans, gaps: drawOrder.gaps ?? [] } } : {}),
|
|
505
|
+
// Only with --contact-keys (#303).
|
|
506
|
+
...(r.feet?.contactKeys !== undefined && contactKeysSnapshot !== null ? { contactKeys: { path: a.contactKeysFile, sha256: createHash('sha256').update(contactKeysSnapshot).digest('hex'), keys: r.feet.contactKeys.keys.length, from: r.feet.contactKeys.from, changed: r.feet.contactKeys.changed, kept: r.feet.contactKeys.kept, unreached: r.feet.contactKeys.unreached } } : {}),
|
|
490
507
|
stance: r.stance === null ? null : { tolerance: r.stance.tolerance, release: r.stance.release, minSeconds: r.stance.minSeconds, bounds: r.stance.bounds, ...(r.stance.range === undefined ? {} : { range: r.stance.range }), runs: r.stance.runs, over: r.stance.over },
|
|
491
508
|
feet: r.feet === null ? null : { planted: r.planted, legLength: r.feet.legLength, straight: r.legStraight, rigLeg: r.rigLeg, segments: r.segments, supports: r.feet.supports, jumps: r.feet.jumps, steps: r.feet.steps, changes: r.feet.changes, ...(r.feet.lowered === undefined ? {} : { lowered: r.feet.lowered }), ...(r.feet.tracked === undefined ? {} : { tracked: r.feet.tracked }), floor: r.floor?.y ?? null, shifts: r.floor?.shifts ?? [], blend: r.blendHold === null ? null : { bounds: r.blendHold.bounds, ...(r.blendHold.range === undefined ? {} : { range: r.blendHold.range }), runs: r.blendHold.runs, over: r.blendHold.over }, reading: feetReading, closure: r.closure },
|
|
492
509
|
shear,
|
package/src/switch.ts
CHANGED
|
@@ -476,8 +476,15 @@ function moveBoundaries(vals: ReadonlyArray<AlternateSet | null>, pos: readonly
|
|
|
476
476
|
return moves;
|
|
477
477
|
}
|
|
478
478
|
|
|
479
|
-
/**
|
|
480
|
-
|
|
479
|
+
/**
|
|
480
|
+
* The key time for a stepped change that shows on frame i: half a frame before it, 0 on the first frame. A key
|
|
481
|
+
* exactly on a frame time is a tie the render breaks either way (its track time is a running sum of 1/fps, key
|
|
482
|
+
* times are held as float32, and a key applies when its time ≤ the track time), so about half such keys land a
|
|
483
|
+
* frame late (rig-c/pose#325); the midpoint sits half a frame from both ties. Every discrete key pose writes goes
|
|
484
|
+
* through it: the attachment and offset keys here, the draw-order keys in `draworder.ts`, and the face and body yaw
|
|
485
|
+
* sets' attachment keys in `retarget.ts` (rig-c/pose#334).
|
|
486
|
+
*/
|
|
487
|
+
export const keyAtFrame = (times: readonly number[], i: number): number => (i === 0 ? 0 : r6((times[i - 1]! + times[i]!) / 2));
|
|
481
488
|
|
|
482
489
|
function keyHandOffset(a: AlternateSet, map: JointMap, cn: string): [number, number] | null {
|
|
483
490
|
const c = map.chains.find((x) => x.name === cn);
|
|
@@ -1116,7 +1123,7 @@ export function selectSwitches(map: JointMap, targets: ReadonlyMap<string, reado
|
|
|
1116
1123
|
const keys: AttachmentKey[] = [];
|
|
1117
1124
|
row.forEach((v, i) => {
|
|
1118
1125
|
if (v === prev) return;
|
|
1119
|
-
keys.push({ t:
|
|
1126
|
+
keys.push({ t: keyAtFrame(times, i), v });
|
|
1120
1127
|
prev = v;
|
|
1121
1128
|
});
|
|
1122
1129
|
if (keys.length > 0) slotTracks.push({ slot: s, property: 'attachment', keys });
|
|
@@ -1129,7 +1136,7 @@ export function selectSwitches(map: JointMap, targets: ReadonlyMap<string, reado
|
|
|
1129
1136
|
row.forEach((v, i) => {
|
|
1130
1137
|
const prev = row[i - 1];
|
|
1131
1138
|
if (i === 0 || (prev![0] === v[0] && prev![1] === v[1])) return;
|
|
1132
|
-
keys.push({ t:
|
|
1139
|
+
keys.push({ t: keyAtFrame(times, i), v: [r3(v[0]), r3(v[1])] });
|
|
1133
1140
|
});
|
|
1134
1141
|
// Every key but the last holds until the next (a stepped curve on the last has nothing to ease to).
|
|
1135
1142
|
for (let k = 0; k + 1 < keys.length; k++) keys[k]!.ease = 'stepped';
|