@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/depth.ts
CHANGED
|
@@ -13,10 +13,17 @@
|
|
|
13
13
|
* | segment | sense when foreshortened | why |
|
|
14
14
|
* | --------- | ------------------------ | --- |
|
|
15
15
|
* | thigh | forward | a hip flexes forward; it extends back too little to foreshorten past the band |
|
|
16
|
-
* | shin |
|
|
16
|
+
* | shin | back | the owner's walk statement (rig-c/pose#336): the lifted foot is behind the supporting leg — a knee folds the shin back |
|
|
17
17
|
* | upper arm | with the opposite leg | in a gait each arm swings with the opposite leg: forward when the other side's thigh is the more foreshortened of the two, back when its own is; forward (a shoulder flexes forward) when neither thigh is foreshortened |
|
|
18
18
|
* | forearm | forward | an elbow bends toward the back of the body, so the forearm folds forward |
|
|
19
19
|
*
|
|
20
|
+
* The thigh's row is the owner's run statement (rig-c/pose#336): the lifted leg
|
|
21
|
+
* is in front, its knee protruding — a raised thigh brings the knee forward.
|
|
22
|
+
* The table is the same in a walk and a run, so `clean --depth walk|run` writes
|
|
23
|
+
* the same signs for both; the gait it records decides which joint
|
|
24
|
+
* `--draw-order depth` orders the legs by (`src/draworder.ts`): a walk by the
|
|
25
|
+
* ankle (the lifted foot behind), a run by the knee (the lifted knee in front).
|
|
26
|
+
*
|
|
20
27
|
* "Forward" is the trunk's facing: toward the camera (+1) when the figure faces
|
|
21
28
|
* it (its left shoulder and hip drawn right of its right ones), away (−1) when
|
|
22
29
|
* it faces away, and undecided (sign 0) when the trunk is side-on — shoulders
|
|
@@ -86,8 +93,8 @@ export interface DerivedDepth {
|
|
|
86
93
|
signs: { toward: number; away: number; flat: number };
|
|
87
94
|
}
|
|
88
95
|
|
|
89
|
-
/** Each frame's depth, derived from the picture's lengths
|
|
90
|
-
export function deriveDepth(track: readonly Joints[]
|
|
96
|
+
/** Each frame's depth, derived from the picture's lengths (the table above; the same for every gait). */
|
|
97
|
+
export function deriveDepth(track: readonly Joints[]): DerivedDepth {
|
|
91
98
|
const lengths = new Map(DEPTH_SEGMENT_NAMES.map((s) => [s, track.map((j) => drawn(j, s))]));
|
|
92
99
|
const rest = {} as DerivedDepth['rest'];
|
|
93
100
|
for (const k of ['upper_arm', 'forearm', 'thigh', 'shin'] as const) {
|
|
@@ -112,7 +119,7 @@ export function deriveDepth(track: readonly Joints[], gait: DepthGait): DerivedD
|
|
|
112
119
|
if (r === null) continue;
|
|
113
120
|
let sense: -1 | 1;
|
|
114
121
|
const kind = kindOf(s);
|
|
115
|
-
if (kind === 'shin') sense =
|
|
122
|
+
if (kind === 'shin') sense = -1;
|
|
116
123
|
else if (kind === 'upper_arm') {
|
|
117
124
|
// The arm swings with the opposite leg: its own thigh and the other side's, by how foreshortened each is.
|
|
118
125
|
const own = ratio(`${s.slice(0, 2)}thigh` as DepthSegment);
|
|
@@ -143,19 +150,52 @@ export function segmentDepth(d: SegmentDepth, length: number): number {
|
|
|
143
150
|
return d.sign * (length / d.ratio) * Math.sqrt(1 - r * r);
|
|
144
151
|
}
|
|
145
152
|
|
|
153
|
+
/**
|
|
154
|
+
* A limb's two joint depths on one frame, px (+ toward the camera): its middle joint (the upper segment's) and its end
|
|
155
|
+
* joint (the middle's plus the lower segment's), from the shoulder or hip at 0, unrounded. Null where the upper segment
|
|
156
|
+
* is not named and placed; `end` null where the lower one is not (the middle joint does not need it: rig-c/pose#336).
|
|
157
|
+
*/
|
|
158
|
+
export function limbJointDepths(joints: Joints, depth: FrameDepth, limb: Limb): { mid: number; end: number | null } | null {
|
|
159
|
+
const [upper, lower] = LIMBS[limb];
|
|
160
|
+
const du = depth[upper];
|
|
161
|
+
const dl = depth[lower];
|
|
162
|
+
const lu = drawn(joints, upper);
|
|
163
|
+
const ll = drawn(joints, lower);
|
|
164
|
+
if (du === undefined || lu === null) return null;
|
|
165
|
+
const mid = segmentDepth(du, lu);
|
|
166
|
+
return { mid, end: dl === undefined || ll === null ? null : mid + segmentDepth(dl, ll) };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* rig-c/pose#336: the frames of a derived depth on which a shin leans the way the trunk faces — the sign table before
|
|
171
|
+
* #336, where a walk's foreshortened shin read forward. The table since leans it back (−facing) in both gaits, and a run's
|
|
172
|
+
* never changed, so only a record a release before #336 cleaned with `--depth walk` carries one. A record with no
|
|
173
|
+
* foreshortened shin reads the same under both tables and is not listed.
|
|
174
|
+
*/
|
|
175
|
+
export function forwardShinFrames(track: readonly Joints[], frames: ReadonlyArray<{ depth?: FrameDepth }>): number[] {
|
|
176
|
+
const out: number[] = [];
|
|
177
|
+
frames.forEach((f, i) => {
|
|
178
|
+
const face = facingOf(track[i] ?? {});
|
|
179
|
+
if (face === 0) return;
|
|
180
|
+
if ((['r_shin', 'l_shin'] as const).some((s) => f.depth?.[s]?.source === 'derived' && f.depth[s]!.sign === face)) out.push(i);
|
|
181
|
+
});
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** The DEPTH_RECLEAN refusal (#336): the file, the gait it records, the frames (the first five), and what to run. */
|
|
186
|
+
export function depthRecleanText(file: string, gait: DepthGait | null, frames: readonly number[]): string {
|
|
187
|
+
const shown = frames.slice(0, 5).join(', ') + (frames.length > 5 ? `, … +${frames.length - 5} more` : '');
|
|
188
|
+
const g = gait ?? 'walk';
|
|
189
|
+
return `${file} carries derived depth (clean --depth ${gait ?? '?'}) whose foreshortened shin leans forward, the way the trunk faces, on ${frames.length} frame(s) (${shown}): the sign table before rig-c/pose#336, where a walk's lifted foot read forward. --draw-order depth reads a walk at the ankle and would draw that foot in front of the supporting leg. Clean it again with this version (rig-pose clean --depth ${g}), which leans a foreshortened shin back in both gaits; a sequence cleaned with --depth run by an earlier release is read as it is (its signs did not change), and measured depth is not checked`;
|
|
190
|
+
}
|
|
191
|
+
|
|
146
192
|
/** The limbs' front-to-back order on one frame, from its depth and its joints' drawn lengths. */
|
|
147
193
|
export function layersOf(joints: Joints, depth: FrameDepth): FrameLayers {
|
|
148
194
|
const z: Partial<Record<Limb, number>> = {};
|
|
149
195
|
for (const limb of LIMB_NAMES) {
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const lu = drawn(joints, upper);
|
|
154
|
-
const ll = drawn(joints, lower);
|
|
155
|
-
if (du === undefined || dl === undefined || lu === null || ll === null) continue;
|
|
156
|
-
const mid = segmentDepth(du, lu);
|
|
157
|
-
const end = mid + segmentDepth(dl, ll);
|
|
158
|
-
z[limb] = Math.round(((mid + end) / 2) * 100) / 100;
|
|
196
|
+
const j = limbJointDepths(joints, depth, limb);
|
|
197
|
+
if (j === null || j.end === null) continue;
|
|
198
|
+
z[limb] = Math.round(((j.mid + j.end) / 2) * 100) / 100;
|
|
159
199
|
}
|
|
160
200
|
const placed = LIMB_NAMES.filter((l) => z[l] !== undefined).sort((a, b) => z[b]! - z[a]! || LIMB_NAMES.indexOf(a) - LIMB_NAMES.indexOf(b));
|
|
161
201
|
const order: Limb[][] = [];
|
package/src/draworder.ts
CHANGED
|
@@ -36,19 +36,38 @@
|
|
|
36
36
|
* ordered, holds the order before; before the first decided frame, the setup
|
|
37
37
|
* order — or, on a looped sequence, the order the clip ends in, held across
|
|
38
38
|
* the seam. A key is written where the order changes, and none while it is the
|
|
39
|
-
* setup order from the start.
|
|
39
|
+
* setup order from the start. Its time is half a frame before the frame it
|
|
40
|
+
* shows on (0 on frame 0), the rule the switches' attachment keys follow
|
|
41
|
+
* (`keyAtFrame` in `switch.ts`): a key exactly on a frame time is a tie the
|
|
42
|
+
* render breaks a frame late about half the time (rig-c/pose#325).
|
|
43
|
+
* - **By the joint the gait names** (rig-c/pose#336, the owner's two
|
|
44
|
+
* statements). On a sequence cleaned with `--depth walk` the pair is ordered
|
|
45
|
+
* by each leg's ankle depth (the hip at 0, plus the thigh's, plus the shin's:
|
|
46
|
+
* `limbJointDepths`' end) — in a walk the lifted foot is behind the supporting
|
|
47
|
+
* leg; with `--depth run` by the knee depth (the thigh's: its middle) — in a
|
|
48
|
+
* run the lifted leg is in front, its knee protruding. The limb mean cannot
|
|
49
|
+
* say either: a lifted leg's thigh forward and shin back cancel in it, and a
|
|
50
|
+
* walk's flat thigh leaves it at most half a shin (35 px on the fixtures),
|
|
51
|
+
* under the limb tie. The tie is scaled to the joint, what a flat chain can
|
|
52
|
+
* hold there: √(1 − 0.95²) × thigh at the knee, × (thigh + shin) at the
|
|
53
|
+
* ankle, the smaller leg's. Measured depth names no gait and keeps the limb
|
|
54
|
+
* mean and its tie (#304).
|
|
40
55
|
*
|
|
41
56
|
* Pure: no clock, no randomness, no disk.
|
|
42
57
|
*/
|
|
43
58
|
import type { Body18 } from './joints.ts';
|
|
44
|
-
import { FLAT_BAND, LIMBS, type FrameLayers, type Limb } from './depth.ts';
|
|
59
|
+
import { FLAT_BAND, LIMBS, limbJointDepths, type FrameLayers, type Limb } from './depth.ts';
|
|
45
60
|
import { knownSetsOf, type JointMap, type Rest, type RigSets } from './map.ts';
|
|
46
|
-
import { DEPTH_SEGMENTS, type DepthSegment, type FrameDepth, type Joint } from './pose.ts';
|
|
61
|
+
import { DEPTH_SEGMENTS, type DepthGait, type DepthSegment, type FrameDepth, type Joint } from './pose.ts';
|
|
62
|
+
import { keyAtFrame } from './switch.ts';
|
|
47
63
|
|
|
48
64
|
/** The limb pairs `--draw-order depth` keys. */
|
|
49
65
|
export const DRAW_ORDER_PAIRS: ReadonlyArray<readonly [Limb, Limb]> = [['r_leg', 'l_leg']];
|
|
50
66
|
/** What one segment inside the flat band can hide out of the picture, per unit of its rest length: √(1 − 0.95²). */
|
|
51
67
|
export const TIE_PER_LENGTH = Math.sqrt(1 - FLAT_BAND[0] * FLAT_BAND[0]);
|
|
68
|
+
/** The joint a gait's legs are ordered by (rig-c/pose#336): a walk's lifted foot is behind, a run's lifted knee in front. */
|
|
69
|
+
export const ORDER_JOINT: Record<DepthGait, LegJoint> = { walk: 'ankle', run: 'knee' };
|
|
70
|
+
export type LegJoint = 'knee' | 'ankle';
|
|
52
71
|
/** The DRAWORDER line's cap, characters (the span list is cut to it; the report carries every span). */
|
|
53
72
|
export const DRAW_ORDER_LINE_MAX = 260;
|
|
54
73
|
|
|
@@ -56,7 +75,7 @@ export interface DrawOrderOffset {
|
|
|
56
75
|
slot: string;
|
|
57
76
|
offset: number;
|
|
58
77
|
}
|
|
59
|
-
/** One key as written into the animation (`t` the frame
|
|
78
|
+
/** One key as written into the animation (`t` half a frame before the frame it shows on, 0 on frame 0: rig-c/pose#325). */
|
|
60
79
|
export interface DrawOrderKey {
|
|
61
80
|
t: number;
|
|
62
81
|
offsets?: DrawOrderOffset[];
|
|
@@ -71,6 +90,8 @@ export interface DrawOrderPair {
|
|
|
71
90
|
keyed: boolean;
|
|
72
91
|
/** #281's tie for the pair, px: depths closer than this hold the order before (null where a rest length is unread: no tie, equal depths only). */
|
|
73
92
|
tie: number | null;
|
|
93
|
+
/** #336: the joint the pair is ordered by, from the gait clean recorded (the tie then the joint's); absent, the limb mean (#304). */
|
|
94
|
+
by?: LegJoint;
|
|
74
95
|
/** Why the pair is not keyed. */
|
|
75
96
|
why?: string;
|
|
76
97
|
/** The slots of anything else drawn between the pair's slots, in setup order (#324: the line counts them first and cuts this list). */
|
|
@@ -80,7 +101,7 @@ export interface DrawOrderPair {
|
|
|
80
101
|
export interface DrawOrderGap {
|
|
81
102
|
a: string;
|
|
82
103
|
b: string;
|
|
83
|
-
/** |depth a − depth b| over the frames both limbs read, px: the median and the largest. */
|
|
104
|
+
/** |depth a − depth b| over the frames both limbs read, px: the median and the largest (at the pair's joint, when it has one). */
|
|
84
105
|
median: number | null;
|
|
85
106
|
max: number | null;
|
|
86
107
|
/** The same as a fraction of the leg (upper + lower rest length of the limb the tie is read from); null where a rest length is unread. */
|
|
@@ -103,7 +124,7 @@ export interface DrawOrderSpan {
|
|
|
103
124
|
}
|
|
104
125
|
export interface DrawOrderReading {
|
|
105
126
|
pairs: DrawOrderPair[];
|
|
106
|
-
/** The `drawOrder` timeline, with the frame each key
|
|
127
|
+
/** The `drawOrder` timeline as written, with the frame each key first shows on. */
|
|
107
128
|
keys: Array<DrawOrderKey & { frame: number }>;
|
|
108
129
|
/** Every span of one decided order, setup or keyed, in frame order per pair. */
|
|
109
130
|
spans: DrawOrderSpan[];
|
|
@@ -225,12 +246,15 @@ function restOf(track: readonly Joints[], depth: ReadonlyArray<FrameDepth | unde
|
|
|
225
246
|
|
|
226
247
|
/**
|
|
227
248
|
* #281's tie for two limbs, px: √(1 − 0.95²) × (upper + lower ÷ 2), the smaller limb's; null where a rest length is unread.
|
|
249
|
+
* By a joint (#336): √(1 − 0.95²) × upper at the knee, × (upper + lower) at the ankle — what a flat chain can hold there.
|
|
228
250
|
* `leg` is that limb's upper + lower rest length (#324's unit for the gap).
|
|
229
251
|
*/
|
|
230
|
-
function tieOf(track: readonly Joints[], depth: ReadonlyArray<FrameDepth | undefined>, limbs: readonly [Limb, Limb]): { tie: number | null; leg: number | null } {
|
|
252
|
+
function tieOf(track: readonly Joints[], depth: ReadonlyArray<FrameDepth | undefined>, limbs: readonly [Limb, Limb], by: LegJoint | undefined): { tie: number | null; leg: number | null } {
|
|
231
253
|
const rests = limbs.map((l) => {
|
|
232
254
|
const [u, w] = LIMBS[l].map((s) => restOf(track, depth, s));
|
|
233
|
-
|
|
255
|
+
// The knee's tie needs the upper segment alone (#336: a leg whose ankle is never placed still orders at the knee).
|
|
256
|
+
if (by === 'knee') return u === null || u === undefined ? null : { hidden: u, leg: w === null || w === undefined ? null : u + w };
|
|
257
|
+
return u === null || u === undefined || w === null || w === undefined ? null : { hidden: by === 'ankle' ? u + w : u + w / 2, leg: u + w };
|
|
234
258
|
});
|
|
235
259
|
if (rests[0] == null || rests[1] == null) return { tie: null, leg: null };
|
|
236
260
|
const r = rests[1].hidden < rests[0].hidden ? rests[1] : rests[0];
|
|
@@ -240,13 +264,13 @@ function tieOf(track: readonly Joints[], depth: ReadonlyArray<FrameDepth | undef
|
|
|
240
264
|
const round = (x: number, k: number): number => Math.round(x * 10 ** k) / 10 ** k;
|
|
241
265
|
|
|
242
266
|
/** One pair's leg depth gap against its tie over the frames (the rule's own test: unequal and at least the tie). */
|
|
243
|
-
function gapOf(pair: DrawOrderPair,
|
|
267
|
+
function gapOf(pair: DrawOrderPair, zs: ReadonlyArray<Partial<Record<Limb, number>>>, leg: number | null): DrawOrderGap {
|
|
244
268
|
const tie = pair.tie ?? 0;
|
|
245
269
|
const gaps: number[] = [];
|
|
246
270
|
let passed = 0;
|
|
247
|
-
for (const
|
|
248
|
-
const za =
|
|
249
|
-
const zb =
|
|
271
|
+
for (const z of zs) {
|
|
272
|
+
const za = z[pair.limbs[0]];
|
|
273
|
+
const zb = z[pair.limbs[1]];
|
|
250
274
|
if (za === undefined || zb === undefined) continue;
|
|
251
275
|
gaps.push(Math.abs(za - zb));
|
|
252
276
|
if (za !== zb && Math.abs(za - zb) >= tie) passed++;
|
|
@@ -262,25 +286,42 @@ function gapOf(pair: DrawOrderPair, layers: readonly FrameLayers[], leg: number
|
|
|
262
286
|
/**
|
|
263
287
|
* The draw-order keys from the frames' layers. `track` and `depth` are the sequence's
|
|
264
288
|
* joints and depth per frame (the rest lengths the tie reads); `order` is the rig's
|
|
265
|
-
* setup draw order (the compiled model's slots); `times` the frames' times.
|
|
289
|
+
* setup draw order (the compiled model's slots); `times` the frames' times. `gait` is
|
|
290
|
+
* the gait clean recorded for its derived depth: a walk orders by the ankle, a run by
|
|
291
|
+
* the knee (#336); none (measured depth) by the limb mean (#304).
|
|
266
292
|
*/
|
|
267
|
-
export function drawOrderFromDepth(layers: readonly FrameLayers[], track: readonly Joints[], depth: ReadonlyArray<FrameDepth | undefined>, times: readonly number[], map: JointMap, rest: Rest, order: ReadonlyArray<{ name: string; bone: string }>, sets?: RigSets, loop = false): DrawOrderReading {
|
|
293
|
+
export function drawOrderFromDepth(layers: readonly FrameLayers[], track: readonly Joints[], depth: ReadonlyArray<FrameDepth | undefined>, times: readonly number[], map: JointMap, rest: Rest, order: ReadonlyArray<{ name: string; bone: string }>, sets?: RigSets, loop = false, gait: DepthGait | null = null): DrawOrderReading {
|
|
294
|
+
const by = gait === null ? undefined : ORDER_JOINT[gait];
|
|
268
295
|
const resolved = DRAW_ORDER_PAIRS.map((p) => resolvePair(p, map, rest, order, sets));
|
|
269
296
|
const legs = resolved.map((r) => {
|
|
270
|
-
const t = tieOf(track, depth, r.pair.limbs);
|
|
297
|
+
const t = tieOf(track, depth, r.pair.limbs, by);
|
|
271
298
|
r.pair.tie = t.tie;
|
|
299
|
+
if (by !== undefined) r.pair.by = by;
|
|
272
300
|
return t.leg;
|
|
273
301
|
});
|
|
302
|
+
// #336: per frame, each limb's depth at the gait's joint (rounded as the layers are); without a gait, the layers' limb means.
|
|
303
|
+
const zs: ReadonlyArray<Partial<Record<Limb, number>>> =
|
|
304
|
+
by === undefined
|
|
305
|
+
? layers.map((l) => l.depth)
|
|
306
|
+
: track.map((j, i) => {
|
|
307
|
+
const z: Partial<Record<Limb, number>> = {};
|
|
308
|
+
for (const limb of new Set(DRAW_ORDER_PAIRS.flat())) {
|
|
309
|
+
const d = depth[i] === undefined ? null : limbJointDepths(j, depth[i]!, limb);
|
|
310
|
+
const v = d === null ? null : by === 'knee' ? d.mid : d.end;
|
|
311
|
+
if (v !== null) z[limb] = Math.round(v * 100) / 100;
|
|
312
|
+
}
|
|
313
|
+
return z;
|
|
314
|
+
});
|
|
274
315
|
const names = order.map((s) => s.name);
|
|
275
316
|
// Per pair, per frame: who is behind ('a' | 'b'), or null for the setup order before any decision.
|
|
276
317
|
const states = resolved.map((r) => {
|
|
277
318
|
const tie = r.pair.tie ?? 0;
|
|
278
319
|
const pass = (start: 'a' | 'b' | null): Array<'a' | 'b' | null> => {
|
|
279
320
|
let state = start;
|
|
280
|
-
return
|
|
321
|
+
return zs.map((z) => {
|
|
281
322
|
if (!r.pair.keyed) return null;
|
|
282
|
-
const za =
|
|
283
|
-
const zb =
|
|
323
|
+
const za = z[r.pair.limbs[0]];
|
|
324
|
+
const zb = z[r.pair.limbs[1]];
|
|
284
325
|
if (za !== undefined && zb !== undefined && za !== zb && Math.abs(za - zb) >= tie) state = za < zb ? 'a' : 'b';
|
|
285
326
|
return state;
|
|
286
327
|
});
|
|
@@ -298,12 +339,14 @@ export function drawOrderFromDepth(layers: readonly FrameLayers[], track: readon
|
|
|
298
339
|
.sort((x, y) => names.indexOf(x.slot) - names.indexOf(y.slot));
|
|
299
340
|
const keys: DrawOrderReading['keys'] = [];
|
|
300
341
|
let last = '[]';
|
|
301
|
-
for (let f = 0; f <
|
|
342
|
+
for (let f = 0; f < zs.length; f++) {
|
|
302
343
|
const offs = offsetsAt(f);
|
|
303
344
|
const text = JSON.stringify(offs);
|
|
304
345
|
if (text === last) continue;
|
|
305
346
|
last = text;
|
|
306
|
-
|
|
347
|
+
// Half a frame before the frame it shows on, as the switches' keys (`keyAtFrame`, rig-c/pose#325).
|
|
348
|
+
const t = keyAtFrame(times, f);
|
|
349
|
+
keys.push(offs.length === 0 ? { frame: f, t } : { frame: f, t, offsets: offs });
|
|
307
350
|
}
|
|
308
351
|
const spans: DrawOrderSpan[] = [];
|
|
309
352
|
resolved.forEach((r, k) => {
|
|
@@ -314,7 +357,7 @@ export function drawOrderFromDepth(layers: readonly FrameLayers[], track: readon
|
|
|
314
357
|
else spans.push({ behind: s === 'a' ? r.pair.a : r.pair.b, front: s === 'a' ? r.pair.b : r.pair.a, frames: [f, f], keyed: (s === 'a' ? r.aBehind : r.bBehind).length > 0 });
|
|
315
358
|
});
|
|
316
359
|
});
|
|
317
|
-
return { pairs: resolved.map((r) => r.pair), keys, spans, gaps: resolved.map((r, k) => gapOf(r.pair,
|
|
360
|
+
return { pairs: resolved.map((r) => r.pair), keys, spans, gaps: resolved.map((r, k) => gapOf(r.pair, zs, legs[k]!)) };
|
|
318
361
|
}
|
|
319
362
|
|
|
320
363
|
/** The animation's `drawOrder` value: the keys without their frames. */
|
|
@@ -330,7 +373,9 @@ const span = (f: [number, number]): string => (f[0] === f[1] ? `${f[0]}` : `${f[
|
|
|
330
373
|
*/
|
|
331
374
|
export function drawOrderLines(d: DrawOrderReading, prefix = 'DRAWORDER '): string[] {
|
|
332
375
|
const keyed = d.spans.filter((s) => s.keyed);
|
|
333
|
-
|
|
376
|
+
// #336: a keyed pair ordered by a joint names it and its joint-scaled tie.
|
|
377
|
+
const joint = d.pairs.find((p) => p.by !== undefined && p.keyed);
|
|
378
|
+
const head = `${prefix}from depth${joint === undefined ? '' : ` by the ${joint.by} (${joint.by === 'ankle' ? 'walk' : 'run'}; tie ${joint.tie === null ? 'none' : `${joint.tie} px`})`}, ${d.keys.length} key(s) over ${keyed.length} span(s)`;
|
|
334
379
|
const groups = new Map<string, string[]>();
|
|
335
380
|
for (const s of keyed) {
|
|
336
381
|
const g = `${s.behind} behind ${s.front}`;
|
|
@@ -366,12 +411,18 @@ export function drawOrderLines(d: DrawOrderReading, prefix = 'DRAWORDER '): stri
|
|
|
366
411
|
/** px with its fraction of the leg: "12.3 px (0.084 leg)". */
|
|
367
412
|
const pxLeg = (px: number | null, legs: number | null): string => (px === null ? 'unread' : `${px} px${legs === null ? '' : ` (${legs} leg)`}`);
|
|
368
413
|
|
|
414
|
+
/** " at the knee" / " at the ankle" for a pair ordered by a joint (#336), else nothing. */
|
|
415
|
+
const gapAt = (d: DrawOrderReading, g: DrawOrderGap): string => {
|
|
416
|
+
const j = d.pairs.find((p) => p.a === g.a && p.b === g.b)?.by;
|
|
417
|
+
return j === undefined ? '' : ` at the ${j}`;
|
|
418
|
+
};
|
|
419
|
+
|
|
369
420
|
/**
|
|
370
421
|
* The DRAWGAP lines (rig-c/pose#324): one per pair, keyed or not — the leg depth gap's median and
|
|
371
422
|
* max against the tie, and how many frames passed it. Cut to `DRAW_ORDER_LINE_MAX` characters.
|
|
372
423
|
*/
|
|
373
424
|
export function drawGapLines(d: DrawOrderReading, prefix = 'DRAWGAP '): string[] {
|
|
374
425
|
return (d.gaps ?? []).map((g) =>
|
|
375
|
-
`${prefix}${g.a} / ${g.b}: leg depth gap median ${pxLeg(g.median, g.medianLegs)}, max ${pxLeg(g.max, g.maxLegs)}, against the tie of ${g.tie === null ? 'none (a rest length is unread; equal depths only)' : pxLeg(g.tie, g.tieLegs)}; ${g.passed} of ${g.frames} frame(s) passed it`.slice(0, DRAW_ORDER_LINE_MAX),
|
|
426
|
+
`${prefix}${g.a} / ${g.b}: leg depth gap${gapAt(d, g)} median ${pxLeg(g.median, g.medianLegs)}, max ${pxLeg(g.max, g.maxLegs)}, against the tie of ${g.tie === null ? 'none (a rest length is unread; equal depths only)' : pxLeg(g.tie, g.tieLegs)}; ${g.passed} of ${g.frames} frame(s) passed it`.slice(0, DRAW_ORDER_LINE_MAX),
|
|
376
427
|
);
|
|
377
428
|
}
|
package/src/legs.ts
CHANGED
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
* frames (`closeLoop`), so the loop carries no net travel.
|
|
59
59
|
*/
|
|
60
60
|
import type { Chain, JointMap, Rest } from './map.ts';
|
|
61
|
+
import type { ContactKeysReading } from './contacts.ts';
|
|
61
62
|
|
|
62
63
|
type Pt = [number, number];
|
|
63
64
|
/** A world 2×2: columns (a, c) — the x axis — and (b, d) — the y axis. */
|
|
@@ -522,6 +523,12 @@ export interface Feet {
|
|
|
522
523
|
* `supportsOf` on every reading.
|
|
523
524
|
*/
|
|
524
525
|
signal?: SupportSignal;
|
|
526
|
+
/**
|
|
527
|
+
* rig-c/pose#303: what `--contact-keys` did — the keys, the frames they cover,
|
|
528
|
+
* the support without them and the runs they changed. Set by `contactKeysOf`,
|
|
529
|
+
* applied last; absent without the flag.
|
|
530
|
+
*/
|
|
531
|
+
contactKeys?: ContactKeysReading;
|
|
525
532
|
}
|
|
526
533
|
|
|
527
534
|
/** A run of frames whose support the lower-ankle rule (rig-c/pose#113) reads differently from the reading before it. */
|