@rig-c/pose 0.17.0 → 0.19.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 +35 -16
- package/cli.ts +18 -12
- package/package.json +1 -1
- package/src/clean.ts +15 -4
- package/src/legs.ts +273 -68
- package/src/map.ts +49 -3
- package/src/pose.ts +86 -32
- package/src/reduce.ts +167 -7
- package/src/retarget.ts +118 -37
- package/src/run.ts +202 -27
- package/src/switch.ts +31 -8
- package/src/verify.ts +66 -16
package/src/retarget.ts
CHANGED
|
@@ -57,6 +57,10 @@
|
|
|
57
57
|
* are posed from the final targets of the bones above. A target out of reach
|
|
58
58
|
* goes to the reach limit along the head-relative direction (REACH); the set's
|
|
59
59
|
* `turn` is part of the solve, and only a forearm it still holds is ART-RANGE.
|
|
60
|
+
* A set with `place: "by-hip"` (rig-c/pose#93) gets the same solve against the
|
|
61
|
+
* hip, in the same pass: each of its chains whose key skeleton puts the wrist
|
|
62
|
+
* within one hip width of the mid-hip lands where the reference's wrist is from
|
|
63
|
+
* its mid-hip, in hip widths.
|
|
60
64
|
*
|
|
61
65
|
* The lines across the body — hips, shoulders, eyes — are read from the pose
|
|
62
66
|
* after holds, whether the map uses them or not: a line under
|
|
@@ -72,12 +76,13 @@ import type { Body18 } from './joints.ts';
|
|
|
72
76
|
import { isBody18, VIRTUAL } from './joints.ts';
|
|
73
77
|
import type { Chain, JointMap, Rest, RigSlots } from './map.ts';
|
|
74
78
|
import { aimAngle, aimEnds, descends, partBone, pointParts, rigPoint } from './map.ts';
|
|
75
|
-
import type { Joint } from './pose.ts';
|
|
79
|
+
import type { Joint, Travel } from './pose.ts';
|
|
80
|
+
import { TRAVEL_IN_PLACE } from './pose.ts';
|
|
76
81
|
import { Problems, Refusal } from './problems.ts';
|
|
77
|
-
import type { ArtRange, Raised, ReachShort, SlotTrack, Switch, SwitchOptions } from './switch.ts';
|
|
82
|
+
import type { ArtRange, ByHip, Raised, ReachShort, SlotTrack, Switch, SwitchOptions } from './switch.ts';
|
|
78
83
|
import { clampArt, coveredChains, isRaised, measureArt, overflow, RANGE_SLACK, reachLimit, runsOf, selectSwitches, twoBone } from './switch.ts';
|
|
79
|
-
import type { BlendHold, Feet, Floor, LegScale, PathPose, Posing, Side, Stance } from './legs.ts';
|
|
80
|
-
import { holdBlend, isLegSegment, legScaleOf, legsOf, pathFrom, plantHip, posePath, rootOf, SIDES, stanceOf, supportsOf, tipOf } from './legs.ts';
|
|
84
|
+
import type { BlendHold, Closure, Feet, Floor, LegScale, PathPose, Posing, Side, Stance } from './legs.ts';
|
|
85
|
+
import { holdBlend, isLegSegment, legScaleOf, legsOf, pathFrom, plantHip, posePath, rootOf, SIDES, stanceOf, stanceRangeOf, supportsOf, tipOf } from './legs.ts';
|
|
81
86
|
|
|
82
87
|
const DEG = 180 / Math.PI;
|
|
83
88
|
|
|
@@ -112,6 +117,8 @@ export interface RetargetOptions extends SwitchOptions {
|
|
|
112
117
|
floor?: number;
|
|
113
118
|
/** While a set whose arm is drawn raised shows, its wrist is placed against the rig's head in eye distances (rig-c/pose#62); false (the default) keeps the pose's angles. */
|
|
114
119
|
raisedByHead?: boolean;
|
|
120
|
+
/** The sequence's `travel` (rig-c/pose#92): "in-place" makes --plant-feet let the supporting foot follow its own track and close the loop; absent, nothing changes. */
|
|
121
|
+
travel?: Travel;
|
|
115
122
|
}
|
|
116
123
|
|
|
117
124
|
export interface Hold {
|
|
@@ -294,12 +301,16 @@ export interface Retarget {
|
|
|
294
301
|
floor: Floor | null;
|
|
295
302
|
/** With --plant-feet: the supporting leg stretched on the blend frames so its ankle stays on the floor (#85). */
|
|
296
303
|
blendHold: BlendHold | null;
|
|
304
|
+
/** With --plant-feet on an in-place sequence: the hip's x drift closed over the loop (#92). */
|
|
305
|
+
closure: Closure | null;
|
|
297
306
|
/** Each ankle's world position on every frame, as the keys put it, when legs are scaled or feet planted. */
|
|
298
307
|
ankles: { bones: Record<Side, string>; at: Record<Side, Pt[]> } | null;
|
|
299
308
|
/** The translated bone's origin against its thigh roots' midpoint, at setup. */
|
|
300
309
|
pivot: Pivot | null;
|
|
301
310
|
/** With --raised-by-head: each raised set's span on each raised chain, its wrist placed against the head. */
|
|
302
311
|
raised: Raised[];
|
|
312
|
+
/** Each span of a `place: "by-hip"` set on each chain it places, its wrist placed against the hip (#93). */
|
|
313
|
+
byHip: ByHip[];
|
|
303
314
|
}
|
|
304
315
|
|
|
305
316
|
/** The hip's pivot against the thigh roots, at setup (rig-c/pose#47). */
|
|
@@ -400,6 +411,9 @@ export function retarget(rest: Rest, map: JointMap, frames: ReadonlyArray<Partia
|
|
|
400
411
|
// eye line with an "a>b" aim, whose two ends are the rig's eyes.
|
|
401
412
|
const head = opts.raisedByHead === true ? headOf(map) : null;
|
|
402
413
|
if (head !== null && 'problem' in head) p.add('RAISED_HEAD', head.problem);
|
|
414
|
+
// `place: "by-hip"` (#93) measures against the hip in hip widths: the map must name the rig's two hip points.
|
|
415
|
+
const hips = map.alternates.some((a) => a.place !== undefined && a.place !== null) ? hipOf(map) : null;
|
|
416
|
+
if (hips !== null && 'problem' in hips) p.add('PLACE_HIP', hips.problem);
|
|
403
417
|
|
|
404
418
|
// Every point the map reads, held where it is not good.
|
|
405
419
|
const needed = new Set<string>();
|
|
@@ -612,6 +626,9 @@ export function retarget(rest: Rest, map: JointMap, frames: ReadonlyArray<Partia
|
|
|
612
626
|
// which are aimed from where the hip is.
|
|
613
627
|
const legScaleOn = opts.legScale !== undefined && opts.legScale !== 'off';
|
|
614
628
|
const plantOn = opts.plantFeet === true;
|
|
629
|
+
/** #92: an in-place sequence under --plant-feet — the supporting foot follows its own track, no step releases, the loop closed. */
|
|
630
|
+
const inPlace = plantOn && opts.travel === TRAVEL_IN_PLACE;
|
|
631
|
+
let closure: Closure | null = null;
|
|
615
632
|
const legScale: LegScale[] = [];
|
|
616
633
|
const scales = new Map<string, number[]>();
|
|
617
634
|
let stance: Stance | null = null;
|
|
@@ -643,7 +660,7 @@ export function retarget(rest: Rest, map: JointMap, frames: ReadonlyArray<Partia
|
|
|
643
660
|
const hipOf = (s: Side) => series.get(`${s}_hip`);
|
|
644
661
|
const ankleOf = (s: Side) => series.get(`${s}_ankle`);
|
|
645
662
|
const hr = hipOf('r'), hl = hipOf('l'), ar = ankleOf('r'), al = ankleOf('l');
|
|
646
|
-
if (hr !== undefined && hl !== undefined && ar !== undefined && al !== undefined) feet = supportsOf({ r: ar, l: al }, { r: hr, l: hl }, keyTimes, secondsOf);
|
|
663
|
+
if (hr !== undefined && hl !== undefined && ar !== undefined && al !== undefined) feet = supportsOf({ r: ar, l: al }, { r: hr, l: hl }, keyTimes, secondsOf, inPlace, plantOn); // #113: planted, the support is read from the lower ankle
|
|
647
664
|
else if (plantOn) p.add('LEGS_MAP', `--plant-feet reads both hips and both ankles from the pose; the map's chains use ${['r_hip', 'l_hip', 'r_ankle', 'l_ankle'].filter((j) => !series.has(j)).join(', ')} nowhere`);
|
|
648
665
|
}
|
|
649
666
|
}
|
|
@@ -695,9 +712,12 @@ export function retarget(rest: Rest, map: JointMap, frames: ReadonlyArray<Partia
|
|
|
695
712
|
});
|
|
696
713
|
const tb = rest.get(t!.bone)!;
|
|
697
714
|
// #85: every support span on one floor — the median of their planted heights, or --floor.
|
|
698
|
-
|
|
715
|
+
// #92: in place, the supporting ankle's own picture x, scaled, is the track its anchor follows.
|
|
716
|
+
const follow = inPlace ? (i: number, s: Side) => series.get(`${s}_ankle`)![i]![0] * scale!.factor : undefined;
|
|
717
|
+
const planted = plantHip(feet!, (i, s) => offsets[i]![s], (i) => poseTravel[i]!, [tb.worldX, tb.worldY], opts.floor, follow);
|
|
699
718
|
const hip = planted.hip;
|
|
700
719
|
floor = planted.floor;
|
|
720
|
+
closure = planted.closure;
|
|
701
721
|
for (let i = 0; i < n; i++) travel[i] = [hip[i]![0] - tb.worldX, hip[i]![1] - tb.worldY];
|
|
702
722
|
const L = (legs as { legs: Record<Side, { chain: Chain; ankleBone: string }> }).legs;
|
|
703
723
|
const world = (i: number, v: Pt): Pt => [hip[i]![0] + v[0], hip[i]![1] + v[1]];
|
|
@@ -725,14 +745,15 @@ export function retarget(rest: Rest, map: JointMap, frames: ReadonlyArray<Partia
|
|
|
725
745
|
};
|
|
726
746
|
// #85: on a blend frame the hip is between two derivations; the supporting leg is stretched
|
|
727
747
|
// (stanceOf's mechanism, its bounds) so its ankle stays on the floor. Before --stance, whose
|
|
728
|
-
// floor is that supporting ankle.
|
|
729
|
-
|
|
748
|
+
// floor is that supporting ankle. #95: the lower bound on a frame is the leg's own range there.
|
|
749
|
+
const ankleSeries = { r: series.get('r_ankle')!, l: series.get('l_ankle')! };
|
|
750
|
+
const lower = stanceRangeOf(ankleSeries, { r: series.get('r_hip')!, l: series.get('l_hip')! });
|
|
751
|
+
blendHold = holdBlend(planted.blends, n, posedFeet, lower);
|
|
730
752
|
stretchChain(blendHold.stretch);
|
|
731
753
|
// --stance (#60): with the hip placed, a free ankle within the tolerance of the floor has its leg
|
|
732
754
|
// stretched (every bone of its chain's scalex, about the chain's root) until it reaches it.
|
|
733
755
|
if (opts.stance !== undefined && opts.stance !== 'off') {
|
|
734
|
-
|
|
735
|
-
stance = stanceOf(feet!, ankleSeries, opts.stance, posedFeet, secondsOf);
|
|
756
|
+
stance = stanceOf(feet!, ankleSeries, opts.stance, posedFeet, secondsOf, lower);
|
|
736
757
|
// The supporting leg (the blend's) and the free leg (the stance's) are different chains on a frame.
|
|
737
758
|
for (let i = 0; i < n; i++) {
|
|
738
759
|
const b = blendHold.stretch[i];
|
|
@@ -881,44 +902,60 @@ export function retarget(rest: Rest, map: JointMap, frames: ReadonlyArray<Partia
|
|
|
881
902
|
// neither. A target out of reach is placed at the reach limit along the head-relative direction
|
|
882
903
|
// (`reachLimit`, a REACH line). The set's `turn` is part of the solve: a held upper arm has its forearm
|
|
883
904
|
// re-aimed at the placed wrist, and only a forearm the turn still holds is ART-RANGE.
|
|
905
|
+
// `place: "by-hip"` (#93) is the same solve against the hip: while a set that asks for it shows, each chain
|
|
906
|
+
// its key skeleton puts the wrist within one hip width of the mid-hip (`byHipChains`) has its wrist placed
|
|
907
|
+
// at the rig's mid-hip + the reference's offset from its mid-hip, in hip widths (each side's median over the
|
|
908
|
+
// span). The rig's hips are the two points `hipOf` reads off the map, posed as the eyes are.
|
|
884
909
|
const raised: Raised[] = [];
|
|
910
|
+
const byHip: ByHip[] = [];
|
|
885
911
|
const raisedArt: Array<{ sw: Switch; entries: ArtRange[] }> = [];
|
|
886
|
-
const raisedJobs: Array<{ sw: Switch; chain: Chain; wrist: (i: number) => Pt; before: Pt[]; reference: Pt[]; placed: Pt[]; refEye: number; rigEye: number; short: Array<{ i: number; how: 'far' | 'near' | 'missed'; shortfall: number }>; upperHeld: number[]; l0: number; l1: number; farthest: number
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
912
|
+
const raisedJobs: Array<{ anchor: Anchor; sw: Switch; chain: Chain; wrist: (i: number) => Pt; before: Pt[]; reference: Pt[]; placed: Pt[]; refEye: number; rigEye: number; short: Array<{ i: number; how: 'far' | 'near' | 'missed'; shortfall: number }>; upperHeld: number[]; l0: number; l1: number; farthest: number }> = [];
|
|
913
|
+
const headOk: Anchor | null = head !== null && !('problem' in head) ? { kind: 'head', joints: head.joints, points: head.points } : null;
|
|
914
|
+
const hipsOk: Anchor | null = hips !== null && !('problem' in hips) ? { kind: 'hip', joints: hips.joints, points: hips.points } : null;
|
|
915
|
+
if (headOk !== null || hipsOk !== null) {
|
|
916
|
+
/** The anchor's two rig points posed on frame i: their midpoint and their distance apart. */
|
|
917
|
+
const pairOn = (an: Anchor, i: number): { centre: Pt; dist: number } => {
|
|
918
|
+
const a = posedPoint(an.points[0], i);
|
|
919
|
+
const b = posedPoint(an.points[1], i);
|
|
891
920
|
return { centre: [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2], dist: Math.hypot(b[0] - a[0], b[1] - a[1]) };
|
|
892
921
|
};
|
|
893
|
-
const headBones = new Set([...head.points.flatMap((x) => pointParts(x).map(partBone))]);
|
|
894
|
-
const ra = series.get(head.joints[0])!;
|
|
895
|
-
const rb = series.get(head.joints[1])!;
|
|
896
|
-
const refCentre = (i: number): Pt => [(ra[i]![0] + rb[i]![0]) / 2, (ra[i]![1] + rb[i]![1]) / 2];
|
|
897
922
|
for (const s of sw.switches) {
|
|
898
923
|
const set = map.alternates.find((a) => a.name === s.set)!;
|
|
899
924
|
const art: ArtRange[] = [];
|
|
900
925
|
for (const cn of s.chains) {
|
|
901
926
|
const c = map.chains.find((x) => x.name === cn)!;
|
|
902
927
|
const drawn = set.drawn[c.bones[0]!];
|
|
903
|
-
|
|
904
|
-
const
|
|
928
|
+
const up = headOk !== null && drawn !== undefined && isRaised(drawn);
|
|
929
|
+
const low = hipsOk !== null && set.place?.chains.includes(c.name) === true;
|
|
930
|
+
if (up && low) {
|
|
931
|
+
p.add('PLACE_BOTH', `set "${set.name}" chain "${c.name}": --raised-by-head places its wrist against the head (bone "${c.bones[0]}" drawn at ${drawn}°, raised), and "place": "by-hip" against the hip (its key skeleton's wrist within one hip width of the mid-hip); a wrist is placed against one — drop "place" from the set or the flag`);
|
|
932
|
+
continue;
|
|
933
|
+
}
|
|
934
|
+
const anchor = up ? headOk : low ? hipsOk : null;
|
|
935
|
+
if (anchor === null) continue;
|
|
936
|
+
const ra = series.get(anchor.joints[0])!;
|
|
937
|
+
const rb = series.get(anchor.joints[1])!;
|
|
938
|
+
const refCentre = (i: number): Pt => [(ra[i]![0] + rb[i]![0]) / 2, (ra[i]![1] + rb[i]![1]) / 2];
|
|
939
|
+
const anchorBones = new Set([...anchor.points.flatMap((x) => pointParts(x).map(partBone))]);
|
|
940
|
+
const label = up ? `--raised-by-head: set "${set.name}" draws chain "${c.name}" raised (bone "${c.bones[0]}" at ${drawn}°)` : `"place": "by-hip": set "${set.name}" places chain "${c.name}"'s wrist against the hip`;
|
|
941
|
+
const CHAIN = up ? 'RAISED_CHAIN' : 'PLACE_CHAIN';
|
|
905
942
|
if (c.bones.length !== 2) {
|
|
906
|
-
p.add(
|
|
943
|
+
p.add(CHAIN, `${label}, and the chain has ${c.bones.length} bone(s); the wrist is placed by a two-bone solve, so it must have two (upper arm, forearm)`);
|
|
907
944
|
continue;
|
|
908
945
|
}
|
|
909
946
|
if (c.reach || c.aim.some((x) => x.includes('>'))) {
|
|
910
|
-
p.add(
|
|
947
|
+
p.add(CHAIN, `${label}, and it aims ${JSON.stringify(c.aim)}${c.reach ? ' with "reach"' : ''}; the solve turns each bone about its own origin toward the next point, so each must aim "x" or at one point`);
|
|
911
948
|
continue;
|
|
912
949
|
}
|
|
913
950
|
const [b0, b1] = c.bones as [string, string];
|
|
914
|
-
const moved = [...
|
|
951
|
+
const moved = [...anchorBones].filter((h) => h === b0 || h === b1 || descends(rest, h, b0));
|
|
915
952
|
if (moved.length > 0) {
|
|
916
|
-
p.add(
|
|
953
|
+
p.add(CHAIN, up ? `${label}, and the head's eye point ${moved.map((h) => `"${h}"`).join(', ')} turns with it; the wrist cannot be placed against a head the arm moves` : `${label}, and the hip point ${moved.map((h) => `"${h}"`).join(', ')} turns with it; the wrist cannot be placed against a hip the arm moves`);
|
|
917
954
|
continue;
|
|
918
955
|
}
|
|
919
956
|
const wristSpec = c.aim[1] === 'x' ? `${b1}:tip` : c.aim[1]!;
|
|
920
957
|
if (c.aim[1] === 'x' && !(rest.get(b1)!.length > 1e-9)) {
|
|
921
|
-
p.add(
|
|
958
|
+
p.add(CHAIN, `${label}, and its last bone "${b1}" aims "x" with no length; its wrist (the tip) is its origin, so there is nothing to place`);
|
|
922
959
|
continue;
|
|
923
960
|
}
|
|
924
961
|
const wristOf = (i: number): Pt => posedPoint(wristSpec, i);
|
|
@@ -927,9 +964,10 @@ export function retarget(rest: Rest, map: JointMap, frames: ReadonlyArray<Partia
|
|
|
927
964
|
const span: number[] = [];
|
|
928
965
|
for (let i = s.frames[0]; i <= s.frames[1]; i++) span.push(i);
|
|
929
966
|
const refEye = median(span.map((i) => Math.hypot(rb[i]![0] - ra[i]![0], rb[i]![1] - ra[i]![1])));
|
|
930
|
-
const rigEye = median(span.map((i) =>
|
|
967
|
+
const rigEye = median(span.map((i) => pairOn(anchor, i).dist));
|
|
931
968
|
if (!(refEye > 1e-9) || !(rigEye > 1e-9)) {
|
|
932
|
-
p.add('RAISED_EYES', `${label}: over frames ${s.frames[0]}–${s.frames[1]} the median eye distance is ${refEye > 1e-9 ? `${rigEye} units on the rig (points "${
|
|
969
|
+
if (up) p.add('RAISED_EYES', `${label}: over frames ${s.frames[0]}–${s.frames[1]} the median eye distance is ${refEye > 1e-9 ? `${rigEye} units on the rig (points "${anchor.points[0]}", "${anchor.points[1]}")` : `${refEye}px in the reference (${anchor.joints[0]}, ${anchor.joints[1]})`}; an eye distance of 0 is no unit`);
|
|
970
|
+
else p.add('PLACE_HIPS', `${label}: over frames ${s.frames[0]}–${s.frames[1]} the median hip width is ${refEye > 1e-9 ? `${rigEye} units on the rig (points "${anchor.points[0]}", "${anchor.points[1]}")` : `${refEye}px in the reference (${anchor.joints[0]}, ${anchor.joints[1]})`}; a hip width of 0 is no unit`);
|
|
933
971
|
continue;
|
|
934
972
|
}
|
|
935
973
|
// The rig's own lengths, and each bone's aim against the vector it carries, at setup.
|
|
@@ -944,7 +982,7 @@ export function retarget(rest: Rest, map: JointMap, frames: ReadonlyArray<Partia
|
|
|
944
982
|
const t1 = targets.get(b1)!;
|
|
945
983
|
const turn0 = set.turn[b0];
|
|
946
984
|
const turn1 = set.turn[b1];
|
|
947
|
-
const job: (typeof raisedJobs)[number] = { sw: s, chain: c, wrist: wristOf, before: [], reference: [], placed: [], refEye, rigEye, short: [], upperHeld: [], l0, l1, farthest: 0
|
|
985
|
+
const job: (typeof raisedJobs)[number] = { anchor, sw: s, chain: c, wrist: wristOf, before: [], reference: [], placed: [], refEye, rigEye, short: [], upperHeld: [], l0, l1, farthest: 0 };
|
|
948
986
|
/** Per frame of the span: the forearm's art turn the re-aim asked, and how far past its turn. */
|
|
949
987
|
const foreAsked: number[] = [];
|
|
950
988
|
const foreOver: number[] = [];
|
|
@@ -953,7 +991,7 @@ export function retarget(rest: Rest, map: JointMap, frames: ReadonlyArray<Partia
|
|
|
953
991
|
const upperOver: number[] = [];
|
|
954
992
|
const heldMiss: number[] = [];
|
|
955
993
|
for (const i of span) {
|
|
956
|
-
const h =
|
|
994
|
+
const h = pairOn(anchor, i);
|
|
957
995
|
const rc = refCentre(i);
|
|
958
996
|
// The reference's wrist from its head centre, in its eye distances, y turned up.
|
|
959
997
|
const ref: Pt = [(refWrist[i]![0] - rc[0]) / refEye, -(refWrist[i]![1] - rc[1]) / refEye];
|
|
@@ -1018,7 +1056,7 @@ export function retarget(rest: Rest, map: JointMap, frames: ReadonlyArray<Partia
|
|
|
1018
1056
|
for (let k = a; k <= b; k++) if (heldMiss[k]! > heldMiss[worst]!) worst = k;
|
|
1019
1057
|
const first = s.frames[0] + a;
|
|
1020
1058
|
const last = s.frames[0] + b;
|
|
1021
|
-
art.push({ set: set.name, chain: c.name, bone: b0, frames: [first, last], seconds: r6(endOf(last) - keyTimes[first]!), maxOverflow: r3(upperOver[worst]!), turn: r3(upperAsked[worst]!), limits: turn0, clamped: true, shortfall: r3(heldMiss[worst]!) });
|
|
1059
|
+
art.push({ set: set.name, chain: c.name, bone: b0, frames: [first, last], seconds: r6(endOf(last) - keyTimes[first]!), maxOverflow: r3(upperOver[worst]!), turn: r3(upperAsked[worst]!), limits: turn0, clamped: true, shortfall: r3(heldMiss[worst]!), ...(up ? {} : { anchor: 'hip' as const }) });
|
|
1022
1060
|
}
|
|
1023
1061
|
}
|
|
1024
1062
|
if (turn1 !== undefined) {
|
|
@@ -1050,8 +1088,8 @@ export function retarget(rest: Rest, map: JointMap, frames: ReadonlyArray<Partia
|
|
|
1050
1088
|
for (const job of raisedJobs) {
|
|
1051
1089
|
const after: Pt[] = [];
|
|
1052
1090
|
for (let i = job.sw.frames[0]; i <= job.sw.frames[1]; i++) {
|
|
1053
|
-
const a = posedPoint(job.
|
|
1054
|
-
const b = posedPoint(job.
|
|
1091
|
+
const a = posedPoint(job.anchor.points[0], i);
|
|
1092
|
+
const b = posedPoint(job.anchor.points[1], i);
|
|
1055
1093
|
const w = job.wrist(i);
|
|
1056
1094
|
after.push([(w[0] - (a[0] + b[0]) / 2) / job.rigEye, (w[1] - (a[1] + b[1]) / 2) / job.rigEye]);
|
|
1057
1095
|
}
|
|
@@ -1065,12 +1103,12 @@ export function retarget(rest: Rest, map: JointMap, frames: ReadonlyArray<Partia
|
|
|
1065
1103
|
short.push({ frames: [job.short[k]!.i, job.short[m]!.i], how: job.short[k]!.how, shortfall: { max: r6(Math.max(...fs)), median: r6(median(fs)) } });
|
|
1066
1104
|
k = m + 1;
|
|
1067
1105
|
}
|
|
1068
|
-
|
|
1106
|
+
const unit = { rig: r6(job.rigEye), reference: r6(job.refEye) };
|
|
1107
|
+
const entry = {
|
|
1069
1108
|
set: job.sw.set,
|
|
1070
1109
|
chain: job.chain.name,
|
|
1071
1110
|
frames: job.sw.frames,
|
|
1072
1111
|
seconds: secondsOf(job.sw.frames),
|
|
1073
|
-
eyes: { rig: r6(job.rigEye), reference: r6(job.refEye) },
|
|
1074
1112
|
reference: round(job.reference),
|
|
1075
1113
|
placed: round(job.placed),
|
|
1076
1114
|
before: round(job.before),
|
|
@@ -1079,7 +1117,11 @@ export function retarget(rest: Rest, map: JointMap, frames: ReadonlyArray<Partia
|
|
|
1079
1117
|
short,
|
|
1080
1118
|
upperHeld: job.upperHeld,
|
|
1081
1119
|
reach: { min: r6(Math.abs(job.l0 - job.l1)), max: r6(job.l0 + job.l1), farthest: r6(job.farthest) },
|
|
1082
|
-
}
|
|
1120
|
+
};
|
|
1121
|
+
// The unit sits after `seconds`, as the report has always written it.
|
|
1122
|
+
const { set, chain, frames, seconds, ...body } = entry;
|
|
1123
|
+
if (job.anchor.kind === 'head') raised.push({ set, chain, frames, seconds, eyes: unit, ...body });
|
|
1124
|
+
else byHip.push({ set, chain, frames, seconds, hips: unit, ...body });
|
|
1083
1125
|
}
|
|
1084
1126
|
|
|
1085
1127
|
// The legs on their final targets: a bone under a scaled bone is keyed through
|
|
@@ -1140,10 +1182,49 @@ export function retarget(rest: Rest, map: JointMap, frames: ReadonlyArray<Partia
|
|
|
1140
1182
|
animation: { duration: keyTimes[n - 1]!, tracks },
|
|
1141
1183
|
times: keyTimes, targets, origin, holds, bends, short, endOn, flips, scale,
|
|
1142
1184
|
switches: sw.switches, shown: sw.shown, distances: sw.distances, turns, turnsUnread, range, artRange,
|
|
1143
|
-
legScale, scales, feet, planted: plantOn, stance, floor, blendHold, ankles, pivot: pivotOf(rest, t?.bone ?? null, legs), raised,
|
|
1185
|
+
legScale, scales, feet, planted: plantOn, stance, floor, blendHold, closure, ankles, pivot: pivotOf(rest, t?.bone ?? null, legs), raised, byHip,
|
|
1144
1186
|
};
|
|
1145
1187
|
}
|
|
1146
1188
|
|
|
1189
|
+
/** What a placed wrist is measured against: the head (#62, the eyes) or the hip (#93, the hips) — two pose joints and the rig's two points. */
|
|
1190
|
+
interface Anchor {
|
|
1191
|
+
kind: 'head' | 'hip';
|
|
1192
|
+
joints: [string, string];
|
|
1193
|
+
points: [string, string];
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
/** The hips as `place: "by-hip"` reads them: two pose joints (r_hip, l_hip) and the rig's two hip points. */
|
|
1197
|
+
interface Hips {
|
|
1198
|
+
joints: [string, string];
|
|
1199
|
+
points: [string, string];
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
/**
|
|
1203
|
+
* The rig's hip points for `place: "by-hip"` (#93): the two points a chain
|
|
1204
|
+
* carrying the hip line (r_hip → l_hip, either way round) names with an "a>b"
|
|
1205
|
+
* aim — a pelvis turned by its hip line; else the origins of the first bones of
|
|
1206
|
+
* the two chains that start at r_hip and at l_hip — the legs, whose roots are
|
|
1207
|
+
* the hip joints; or why the map names neither.
|
|
1208
|
+
*/
|
|
1209
|
+
function hipOf(map: JointMap): Hips | { problem: string } {
|
|
1210
|
+
for (const c of map.chains) {
|
|
1211
|
+
for (let k = 0; k + 1 < c.joints.length; k++) {
|
|
1212
|
+
const a = c.joints[k]!;
|
|
1213
|
+
const b = c.joints[k + 1]!;
|
|
1214
|
+
if (!((a === 'r_hip' && b === 'l_hip') || (a === 'l_hip' && b === 'r_hip'))) continue;
|
|
1215
|
+
const aim = c.aim[k]!;
|
|
1216
|
+
if (!aim.includes('>')) continue;
|
|
1217
|
+
const [pa, pb] = aimEnds(c.bones[k]!, aim);
|
|
1218
|
+
return { joints: [a, b], points: [pa, pb] };
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
const from = (j: string, other: string) => map.chains.find((c) => c.joints[0] === j && c.joints[1] !== other);
|
|
1222
|
+
const r = from('r_hip', 'l_hip');
|
|
1223
|
+
const l = from('l_hip', 'r_hip');
|
|
1224
|
+
if (r !== undefined && l !== undefined) return { joints: ['r_hip', 'l_hip'], points: [r.bones[0]!, l.bones[0]!] };
|
|
1225
|
+
return { problem: `"place": "by-hip" places a wrist against the hip in hip widths, read on the rig between two hip points: the two a chain carrying the hip line (r_hip → l_hip) names with an "a>b" aim, or else the first bones' origins of the two chains starting at r_hip and l_hip (the legs); the map has neither (${r === undefined && l === undefined ? 'no chain starts at r_hip or l_hip' : `no chain starts at ${r === undefined ? 'r_hip' : 'l_hip'}`})` };
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1147
1228
|
/** The head as --raised-by-head reads it: the chain carrying the eye line, its two pose joints and the rig's two eye points. */
|
|
1148
1229
|
interface Head {
|
|
1149
1230
|
chain: string;
|