@rig-c/pose 0.5.0 → 0.6.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 +2 -0
- package/package.json +1 -1
- package/src/map.ts +13 -3
- package/src/switch.ts +8 -1
package/README.md
CHANGED
|
@@ -172,6 +172,8 @@ Bone rotation cannot carry every pose: an arm behind the head, a forearm toward
|
|
|
172
172
|
**TURN.** The cutoff was measured on the three lines across the body before it was fixed ([#6](https://github.com/rig-c/pose/issues/6)): on the 13 s front-facing dance their picture length never drops under 0.81 of its median and none reverses; on a second sequence the hip line falls to 0.17 at frames 465–491 (side-on) and the eye line to 0.22 at 26–58 (head in profile), and 3 frames reverse. 0.5 lies between. A line is reversed when it points more than 90° from the subject's right joint to the left (front-facing, it points at 0°). The lines are read after holds and smoothing, held and smoothed as the map's points are even where the map does not use them; a line whose points are good on no frame is named as not measured.
|
|
173
173
|
|
|
174
174
|
|
|
175
|
+
**A stopgap window: `frames`.** A set may state `"frames": [[first, last], …]`: it shows only inside those windows, and on them it is chosen over any set within tolerance that has no window. It is for two sets whose key skeletons the angles cannot tell apart. Measured on HQ's dancer, the hands clasped behind the back and the hands low in front differ by only 2–21°, and the wrist and hand-point scores do not separate them either; the cue that might is #33's depth state. It is recorded in the map as a stopgap, not a selection rule.
|
|
176
|
+
|
|
175
177
|
**How far a set's art may turn: `turn`.** A drawing reads right only near the angle it was painted at. A set may state, per bone with a `drawn` angle, how far its art may turn from it, `"turn": { "sleeve_r0": [-20, 20] }`. While the set shows, the bone is held within it: its target becomes drawn + clamp(wrap(target − drawn), min, max), each bone on its own world angle, so a forearm under a held upper arm keeps its own target. The landing check measures against the held angle. The overflow the pose asked for is listed as `ART-RANGE`. A `turn` on a bone the set has no `drawn` angle for is refused (`MAP_ALT_TURN`).
|
|
176
178
|
## Requirements
|
|
177
179
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rig-c/pose",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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/map.ts
CHANGED
|
@@ -93,6 +93,8 @@ export interface AlternateSet {
|
|
|
93
93
|
offsets: Record<string, [number, number]>;
|
|
94
94
|
/** Bone → how far its art may turn from `drawn` while the set shows, [min, max] degrees: the bone is clamped there, and the overflow is ART-RANGE demand. */
|
|
95
95
|
turn: Record<string, [number, number]>;
|
|
96
|
+
/** A stopgap: the only frames the set may show on, a list of [first, last] windows; on them it is chosen over any set without windows. Null: any frame. */
|
|
97
|
+
frames: Array<[number, number]> | null;
|
|
96
98
|
}
|
|
97
99
|
export interface JointMap {
|
|
98
100
|
chains: Chain[];
|
|
@@ -226,7 +228,7 @@ export function readMap(text: string, where: string): JointMap {
|
|
|
226
228
|
return { chains, translate, alternates };
|
|
227
229
|
}
|
|
228
230
|
|
|
229
|
-
const SET_SHAPE = '{ name, slots: { "<slot>": "<attachment>" }, hides?: [slot], chains: [chain], pose: { "<joint>": [x, y] }, drawn?: { "<bone>": deg }, offsets?: { "<bone>": [dx, dy] }, turn?: { "<bone>": [min, max] } }';
|
|
231
|
+
const SET_SHAPE = '{ name, slots: { "<slot>": "<attachment>" }, hides?: [slot], chains: [chain], pose: { "<joint>": [x, y] }, drawn?: { "<bone>": deg }, offsets?: { "<bone>": [dx, dy] }, turn?: { "<bone>": [min, max] }, frames?: [[first, last], …] }';
|
|
230
232
|
|
|
231
233
|
/** `alternates`: each set's shape, its chains against the map's, its key skeleton against those chains' joints, and its bones against those chains' bones. */
|
|
232
234
|
function readAlternates(p: Problems, where: string, raw: unknown, chains: readonly Chain[], translate: Translate | null): AlternateSet[] {
|
|
@@ -243,7 +245,7 @@ function readAlternates(p: Problems, where: string, raw: unknown, chains: readon
|
|
|
243
245
|
p.add('MAP_ALT_SHAPE', `${aw} is ${typeName(a)}; a set is ${SET_SHAPE}`);
|
|
244
246
|
return;
|
|
245
247
|
}
|
|
246
|
-
refuseUnknownKeys(p, 'MAP_KEYS', aw, a, ['name', 'note', 'slots', 'hides', 'chains', 'pose', 'drawn', 'offsets', 'turn']);
|
|
248
|
+
refuseUnknownKeys(p, 'MAP_KEYS', aw, a, ['name', 'note', 'slots', 'hides', 'chains', 'pose', 'drawn', 'offsets', 'turn', 'frames']);
|
|
247
249
|
const name = typeof a.name === 'string' && a.name !== '' ? a.name : null;
|
|
248
250
|
if (name === null) p.add('MAP_ALT_NAME', `${aw} "name" is ${typeName(a.name)}; it must be a non-empty string`);
|
|
249
251
|
else if (names.has(name)) p.add('MAP_ALT_NAME', `${aw} "name" "${name}" names two sets`);
|
|
@@ -344,7 +346,15 @@ function readAlternates(p: Problems, where: string, raw: unknown, chains: readon
|
|
|
344
346
|
}
|
|
345
347
|
}
|
|
346
348
|
}
|
|
347
|
-
|
|
349
|
+
let frames: Array<[number, number]> | null = null;
|
|
350
|
+
if ('frames' in a) {
|
|
351
|
+
const f = a.frames;
|
|
352
|
+
const pair = (w: unknown): w is [number, number] => Array.isArray(w) && w.length === 2 && w.every((x) => Number.isInteger(x) && (x as number) >= 0) && (w[0] as number) <= (w[1] as number);
|
|
353
|
+
if (!Array.isArray(f) || f.length === 0 || !f.every(pair)) {
|
|
354
|
+
p.add('MAP_ALT_FRAMES', `${label} "frames" is ${JSON.stringify(f)}; it must be a non-empty list of windows [[first, last], …], whole frame numbers ≥ 0 with first ≤ last`);
|
|
355
|
+
} else frames = (f as Array<[number, number]>).map((w) => [w[0], w[1]]);
|
|
356
|
+
}
|
|
357
|
+
if (ok && name !== null && cs !== null) out.push({ name, slots, hides, chains: cs, pose, drawn, offsets, turn, frames });
|
|
348
358
|
});
|
|
349
359
|
return out;
|
|
350
360
|
}
|
package/src/switch.ts
CHANGED
|
@@ -186,13 +186,20 @@ export function selectSwitches(map: JointMap, targets: ReadonlyMap<string, reado
|
|
|
186
186
|
for (const group of competing(map.alternates)) {
|
|
187
187
|
const pick: Array<AlternateSet | null> = [];
|
|
188
188
|
for (let i = 0; i < n; i++) {
|
|
189
|
+
// A set with a `frames` window shows only inside it, and there it is chosen
|
|
190
|
+
// over any set without one (a stopgap for what the angles cannot tell apart).
|
|
189
191
|
let best: AlternateSet | null = null;
|
|
190
192
|
let bestD = Infinity;
|
|
193
|
+
let bestWindowed = false;
|
|
191
194
|
for (const a of group) {
|
|
195
|
+
if (a.frames !== null && !a.frames.some(([f, l]) => i >= f && i <= l)) continue;
|
|
192
196
|
const d = distances.get(a.name)![i]!;
|
|
193
|
-
if (d
|
|
197
|
+
if (d > opts.switchTolerance) continue;
|
|
198
|
+
const windowed = a.frames !== null;
|
|
199
|
+
if ((windowed && !bestWindowed) || (windowed === bestWindowed && d < bestD)) {
|
|
194
200
|
best = a;
|
|
195
201
|
bestD = d;
|
|
202
|
+
bestWindowed = windowed;
|
|
196
203
|
}
|
|
197
204
|
}
|
|
198
205
|
pick.push(best);
|