@rig-c/pose 0.4.0 → 0.5.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 +4 -2
- package/package.json +1 -1
- package/src/map.ts +16 -3
- package/src/retarget.ts +2 -1
- package/src/switch.ts +31 -6
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ and what the run cannot decide for you it lists:
|
|
|
48
48
|
- `SWITCH` — with `alternates` in the map ([below](#alternate-drawings-alternates)): each span on which a set of alternate drawings shows, its slots, its frames, how far the pose was from the set's key skeleton, and how far each bone with a `drawn` angle was turned from it.
|
|
49
49
|
- `UNCOVERED` — with `alternates` in the map: every fold on a frame where no switch took the folding mesh off screen. This is the list of poses still to paint.
|
|
50
50
|
- `RANGE` — a chain's `range` ([below](#a-joints-range-range)): a bone whose rotate key the pose asks past its range, outside every span of a set depicting its chain, is clamped at the limit; one line per run, with the frames, the seconds and the largest overflow. The demand for an alternate.
|
|
51
|
-
- `ART-RANGE` — inside a set's span: a bone with
|
|
51
|
+
- `ART-RANGE` — inside a set's span: a bone with a `drawn` angle in the set, turned from it past its limits — the set's `turn` for that bone (held at the limit) or, without one, its chain's `range` (the bone follows the pose). Listed as the pose asked it either way: the demand for another drawing of that set.
|
|
52
52
|
- `TURN` — a line across the body (hips, shoulders, eyes) shorter in the picture than 0.5 of its median length, or reversed, for at least 0.15 s: the body or the head turned. A reading of the pose, made whether or not the map uses the line; it changes no key. The demand for a turned-body drawing, as `END-ON` is for a foreshortened limb.
|
|
53
53
|
|
|
54
54
|
`--report <file>` writes all of it as JSON (`rig-pose-report/1`): `options` (every figure the run used, `switchTolerance` and `switchMin` included), `scale`, `holds`, `short`, `endOn`, `bends`, `flips`, `gate`, `landings`, `posture`, `folds`, and the demand lists:
|
|
@@ -58,7 +58,7 @@ and what the run cannot decide for you it lists:
|
|
|
58
58
|
- `endOn` — above, under the short hold.
|
|
59
59
|
- `turns: [{ line, joints, frames, seconds, minFraction, reversed }]` — `line` is `hip`, `shoulder` or `eye`.
|
|
60
60
|
- `range: [{ chain, bone, frames, seconds, maxOverflow, key, limits }]` — `key` is the rotate key the pose asked for on the frame of the largest overflow, before the clamp.
|
|
61
|
-
- `artRange: [{ set, chain, bone, frames, seconds, maxOverflow, turn, limits }]` — `turn` is the signed art turn on that frame.
|
|
61
|
+
- `artRange: [{ set, chain, bone, frames, seconds, maxOverflow, turn, limits, clamped }]` — `turn` is the signed art turn the pose asked on that frame; `clamped` says whether the set's `turn` held the bone there.
|
|
62
62
|
|
|
63
63
|
Runs in `turns`, `range` and `artRange` merge across a gap of one frame, as `endOn` does; `seconds` runs from the first frame's key time to the end of the last frame.
|
|
64
64
|
|
|
@@ -171,6 +171,8 @@ Bone rotation cannot carry every pose: an arm behind the head, a forearm toward
|
|
|
171
171
|
|
|
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
|
+
|
|
175
|
+
**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`).
|
|
174
176
|
## Requirements
|
|
175
177
|
|
|
176
178
|
[Bun](https://bun.sh) ≥ 1.2 on `PATH` — rig-pose, like rig-c, runs its TypeScript sources directly. `rig-c` comes as a dependency. With `@esotericsoftware/spine-core` installed beside it, rig-c's gate runs the spine-core round trip; without it, rig-c's own gate, and the gate's report says which.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rig-c/pose",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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
|
@@ -91,6 +91,8 @@ export interface AlternateSet {
|
|
|
91
91
|
drawn: Record<string, number>;
|
|
92
92
|
/** Bone → a translate, in its parent's axes, held while the set shows. */
|
|
93
93
|
offsets: Record<string, [number, number]>;
|
|
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
|
+
turn: Record<string, [number, number]>;
|
|
94
96
|
}
|
|
95
97
|
export interface JointMap {
|
|
96
98
|
chains: Chain[];
|
|
@@ -224,7 +226,7 @@ export function readMap(text: string, where: string): JointMap {
|
|
|
224
226
|
return { chains, translate, alternates };
|
|
225
227
|
}
|
|
226
228
|
|
|
227
|
-
const SET_SHAPE = '{ name, slots: { "<slot>": "<attachment>" }, hides?: [slot], chains: [chain], pose: { "<joint>": [x, y] }, drawn?: { "<bone>": deg }, offsets?: { "<bone>": [dx, dy] } }';
|
|
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] } }';
|
|
228
230
|
|
|
229
231
|
/** `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. */
|
|
230
232
|
function readAlternates(p: Problems, where: string, raw: unknown, chains: readonly Chain[], translate: Translate | null): AlternateSet[] {
|
|
@@ -241,7 +243,7 @@ function readAlternates(p: Problems, where: string, raw: unknown, chains: readon
|
|
|
241
243
|
p.add('MAP_ALT_SHAPE', `${aw} is ${typeName(a)}; a set is ${SET_SHAPE}`);
|
|
242
244
|
return;
|
|
243
245
|
}
|
|
244
|
-
refuseUnknownKeys(p, 'MAP_KEYS', aw, a, ['name', 'note', 'slots', 'hides', 'chains', 'pose', 'drawn', 'offsets']);
|
|
246
|
+
refuseUnknownKeys(p, 'MAP_KEYS', aw, a, ['name', 'note', 'slots', 'hides', 'chains', 'pose', 'drawn', 'offsets', 'turn']);
|
|
245
247
|
const name = typeof a.name === 'string' && a.name !== '' ? a.name : null;
|
|
246
248
|
if (name === null) p.add('MAP_ALT_NAME', `${aw} "name" is ${typeName(a.name)}; it must be a non-empty string`);
|
|
247
249
|
else if (names.has(name)) p.add('MAP_ALT_NAME', `${aw} "name" "${name}" names two sets`);
|
|
@@ -331,7 +333,18 @@ function readAlternates(p: Problems, where: string, raw: unknown, chains: readon
|
|
|
331
333
|
}
|
|
332
334
|
}
|
|
333
335
|
}
|
|
334
|
-
|
|
336
|
+
const turn: Record<string, [number, number]> = {};
|
|
337
|
+
if ('turn' in a) {
|
|
338
|
+
if (!isObject(a.turn)) p.add('MAP_ALT_TURN', `${label} "turn" is ${typeName(a.turn)}; it is { "<bone>": [min, max] }, how far each bone's art may turn from its drawn angle, degrees`);
|
|
339
|
+
else {
|
|
340
|
+
for (const [b, v] of Object.entries(a.turn)) {
|
|
341
|
+
if (!(b in drawn)) p.add('MAP_ALT_TURN', `${label} "turn" names bone "${b}", which has no "drawn" angle in the set; a turn is measured from the drawn angle (the set draws ${Object.keys(drawn).join(', ') || 'none'})`);
|
|
342
|
+
else if (!Array.isArray(v) || v.length !== 2 || !v.every((x) => typeof x === 'number' && Number.isFinite(x)) || (v[0] as number) > (v[1] as number)) p.add('MAP_ALT_TURN', `${label} "turn" bone "${b}" is ${JSON.stringify(v)}; it must be [min, max], two finite numbers of degrees with min ≤ max`);
|
|
343
|
+
else turn[b] = [v[0] as number, v[1] as number];
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (ok && name !== null && cs !== null) out.push({ name, slots, hides, chains: cs, pose, drawn, offsets, turn });
|
|
335
348
|
});
|
|
336
349
|
return out;
|
|
337
350
|
}
|
package/src/retarget.ts
CHANGED
|
@@ -67,7 +67,7 @@ import { aimAngle, rigPoint } from './map.ts';
|
|
|
67
67
|
import type { Joint } from './pose.ts';
|
|
68
68
|
import { Problems } from './problems.ts';
|
|
69
69
|
import type { ArtRange, SlotTrack, Switch, SwitchOptions } from './switch.ts';
|
|
70
|
-
import { coveredChains, measureArt, overflow, RANGE_SLACK, runsOf, selectSwitches } from './switch.ts';
|
|
70
|
+
import { clampArt, coveredChains, measureArt, overflow, RANGE_SLACK, runsOf, selectSwitches } from './switch.ts';
|
|
71
71
|
|
|
72
72
|
const DEG = 180 / Math.PI;
|
|
73
73
|
|
|
@@ -682,6 +682,7 @@ export function retarget(rest: Rest, map: JointMap, frames: ReadonlyArray<Partia
|
|
|
682
682
|
}
|
|
683
683
|
p.throwIfAny();
|
|
684
684
|
const artRange = measureArt(sw.switches, map, targets, endOf, keyTimes, RANGE_GAP);
|
|
685
|
+
clampArt(sw.switches, map, targets);
|
|
685
686
|
|
|
686
687
|
// Keys: the bone's turn less the turn of the nearest mapped bone above it.
|
|
687
688
|
const tracks: Array<MotionTrack | SlotTrack> = [];
|
package/src/switch.ts
CHANGED
|
@@ -90,6 +90,8 @@ export interface ArtRange {
|
|
|
90
90
|
turn: number;
|
|
91
91
|
/** The bone's range, degrees. */
|
|
92
92
|
limits: [number, number];
|
|
93
|
+
/** Whether the bone was held at the limit (the set's `turn`) or followed the pose (its chain's `range` only). */
|
|
94
|
+
clamped: boolean;
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
export interface Uncovered {
|
|
@@ -321,9 +323,11 @@ export const overflow = (v: number, [min, max]: readonly [number, number]): numb
|
|
|
321
323
|
|
|
322
324
|
/**
|
|
323
325
|
* The art turn of each switch, in place (`artTurn`), and the art-range demand:
|
|
324
|
-
* inside a span, per bone with
|
|
325
|
-
* its chain
|
|
326
|
-
* outside
|
|
326
|
+
* inside a span, per bone with a `drawn` angle in the set and limits — the
|
|
327
|
+
* set's `turn` for it, else its chain's `range` — every frame whose signed art
|
|
328
|
+
* turn wrap(target − drawn) falls outside them, in runs merged across a gap of
|
|
329
|
+
* `gap` frames. Measured on the targets as the pose asks them, before
|
|
330
|
+
* `clampArt` holds the set's `turn` limits.
|
|
327
331
|
*/
|
|
328
332
|
export function measureArt(switches: Switch[], map: JointMap, targets: ReadonlyMap<string, readonly number[]>, endOf: (i: number) => number, times: readonly number[], gap: number): ArtRange[] {
|
|
329
333
|
const out: ArtRange[] = [];
|
|
@@ -336,7 +340,7 @@ export function measureArt(switches: Switch[], map: JointMap, targets: ReadonlyM
|
|
|
336
340
|
const abs = turns.map(Math.abs);
|
|
337
341
|
sw.artTurn[bone] = { max: r3(Math.max(...abs)), median: r3(median(abs)) };
|
|
338
342
|
const chain = map.chains.find((c) => c.bones.includes(bone))!;
|
|
339
|
-
const limits = chain.range[bone];
|
|
343
|
+
const limits = set.turn[bone] ?? chain.range[bone];
|
|
340
344
|
if (limits === undefined) continue;
|
|
341
345
|
const flag = turns.map((t) => overflow(t, limits) > RANGE_SLACK);
|
|
342
346
|
for (const [a, b] of runsOf(flag, gap)) {
|
|
@@ -344,7 +348,7 @@ export function measureArt(switches: Switch[], map: JointMap, targets: ReadonlyM
|
|
|
344
348
|
for (let k = a; k <= b; k++) if (overflow(turns[k]!, limits) > overflow(turns[worst]!, limits)) worst = k;
|
|
345
349
|
const first = sw.frames[0] + a;
|
|
346
350
|
const last = sw.frames[0] + b;
|
|
347
|
-
out.push({ set: sw.set, chain: chain.name, bone, frames: [first, last], seconds: r6(endOf(last) - times[first]!), maxOverflow: r3(overflow(turns[worst]!, limits)), turn: r3(turns[worst]!), limits });
|
|
351
|
+
out.push({ set: sw.set, chain: chain.name, bone, frames: [first, last], seconds: r6(endOf(last) - times[first]!), maxOverflow: r3(overflow(turns[worst]!, limits)), turn: r3(turns[worst]!), limits, clamped: bone in set.turn });
|
|
348
352
|
}
|
|
349
353
|
}
|
|
350
354
|
}
|
|
@@ -426,5 +430,26 @@ export function switchLine(s: Switch, times: readonly number[]): string {
|
|
|
426
430
|
}
|
|
427
431
|
|
|
428
432
|
export function artRangeLine(e: ArtRange, times: readonly number[]): string {
|
|
429
|
-
return `set "${e.set}" chain "${e.chain}" bone "${e.bone}" turned ${sg(e.turn)} from drawn (range [${e.limits[0]}, ${e.limits[1]}]) ${spanOf(e.frames, times)}: ${e.seconds.toFixed(3)}s, up to ${deg(e.maxOverflow)} past it — demand for another drawing of the set`;
|
|
433
|
+
return `set "${e.set}" chain "${e.chain}" bone "${e.bone}" turned ${sg(e.turn)} from drawn (range [${e.limits[0]}, ${e.limits[1]}]) ${spanOf(e.frames, times)}: ${e.seconds.toFixed(3)}s, up to ${deg(e.maxOverflow)} past it; ${e.clamped ? 'held at the limit' : 'the bone follows the pose'} — demand for another drawing of the set`;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Inside each span, a bone with a `turn` in the set is held within it: its
|
|
438
|
+
* target world angle is drawn + clamp(wrap(target − drawn), min, max). Each
|
|
439
|
+
* bone is clamped on its own world angle, and keys are differences of world
|
|
440
|
+
* angles, so a forearm under a clamped upper arm keeps its own target.
|
|
441
|
+
*/
|
|
442
|
+
export function clampArt(switches: readonly Switch[], map: JointMap, targets: Map<string, number[]>): void {
|
|
443
|
+
for (const sw of switches) {
|
|
444
|
+
const set = map.alternates.find((a) => a.name === sw.set)!;
|
|
445
|
+
for (const [bone, limits] of Object.entries(set.turn)) {
|
|
446
|
+
const drawn = set.drawn[bone]!;
|
|
447
|
+
const target = targets.get(bone)!;
|
|
448
|
+
for (let i = sw.frames[0]; i <= sw.frames[1]; i++) {
|
|
449
|
+
const t = wrap(target[i]! - drawn);
|
|
450
|
+
const c = Math.min(Math.max(t, limits[0]), limits[1]);
|
|
451
|
+
if (Math.abs(c - t) > RANGE_SLACK) target[i] = wrap(drawn + c);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
430
455
|
}
|