@rig-c/pose 0.22.0 → 0.22.1
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 +1 -0
- package/package.json +1 -1
- package/src/reduce.ts +359 -0
- package/src/run.ts +10 -1
package/README.md
CHANGED
|
@@ -253,6 +253,7 @@ The retarget writes one key per pose frame on every mapped bone. That is faithfu
|
|
|
253
253
|
- A scaled bone's `scalex` track is thinned within 5e-5 of its setup length: half of the scale landing check's 1e-4, so a reduced curve cannot fail that check.
|
|
254
254
|
- A mapped bone under a scaled bone (keyed through its parent's posed matrix by `--leg-scale`) is solved again through its parent as reduced. Its rotate is then thinned with the error read as the world aim it lands on, and its `scalex` with the error read as the world length it lands on. Its world error is its own fit error, as down any other chain.
|
|
255
255
|
- Each ankle is held to the plant's tolerance plus the most the reduction may move it: the hip's tolerance (with `--reduce-hip`), plus the leg from the hip to the ankle (each bone's setup offset from its parent and the last bone's length) times the largest scale asked, times the angle tolerance in radians plus 5e-5. The ankle's `LAND` line prints that tolerance.
|
|
256
|
+
- When `--leg-scale` is active, each trunk bone (`chest` and `head`, not an ancestor of any scaled bone) has its kept knots and Bézier handles smoothed within `deg` of its dense world aim to minimise the mean absolute second difference of its evaluated local angle and its child's compensated local angle, holding any limb children within 0.03° of their fitted world aim (rig-c/pose#116).
|
|
256
257
|
|
|
257
258
|
Exit codes: `0` written, `1` refused (every reason printed, nothing written), `2` bad invocation. `rig-pose --help` has every flag.
|
|
258
259
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rig-c/pose",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.1",
|
|
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/reduce.ts
CHANGED
|
@@ -379,6 +379,365 @@ export function reduceKeys(times: readonly number[], bones: readonly ReduceBone[
|
|
|
379
379
|
return { rotate: ordered, hip: hipKeys, rows, errors: errorsOrdered };
|
|
380
380
|
}
|
|
381
381
|
|
|
382
|
+
// ---------------------------------------------------------------------------
|
|
383
|
+
// trunk-chain smoothing within the stated tolerance (rig-c/pose#116)
|
|
384
|
+
// ---------------------------------------------------------------------------
|
|
385
|
+
|
|
386
|
+
interface TrackBasis {
|
|
387
|
+
keys: readonly ReducedKey[];
|
|
388
|
+
samples: readonly number[];
|
|
389
|
+
K: number;
|
|
390
|
+
spans: number;
|
|
391
|
+
P: number;
|
|
392
|
+
hx1s: number[];
|
|
393
|
+
hx2s: number[];
|
|
394
|
+
f0: number[];
|
|
395
|
+
cols: Float64Array[];
|
|
396
|
+
d2Cols: Float64Array[];
|
|
397
|
+
activeFrames: number[][];
|
|
398
|
+
activeD2: number[][];
|
|
399
|
+
x0: Float64Array;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function evalTrack1D(keys: readonly ReducedKey[], samples: readonly number[]): number[] {
|
|
403
|
+
return trackAt(keys, samples, 1).map((v) => v[0]!);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function buildBasis(keys: readonly ReducedKey[], samples: readonly number[]): TrackBasis {
|
|
407
|
+
const K = keys.length;
|
|
408
|
+
const n = samples.length;
|
|
409
|
+
const spans = K - 1;
|
|
410
|
+
const hx1s: number[] = [];
|
|
411
|
+
const hx2s: number[] = [];
|
|
412
|
+
for (let j = 0; j < spans; j++) {
|
|
413
|
+
const t0 = keys[j]!.t;
|
|
414
|
+
const t1 = keys[j + 1]!.t;
|
|
415
|
+
hx1s.push(t0 + (t1 - t0) / 3);
|
|
416
|
+
hx2s.push(t0 + (2 * (t1 - t0)) / 3);
|
|
417
|
+
}
|
|
418
|
+
const zeroKeys: ReducedKey[] = keys.map((k, j) => (j + 1 < K ? { t: k.t, v: [0], curve: [hx1s[j]!, 0, hx2s[j]!, 0] } : { t: k.t, v: [0] }));
|
|
419
|
+
const f0 = evalTrack1D(zeroKeys, samples);
|
|
420
|
+
const P = K + 2 * spans;
|
|
421
|
+
const cols: Float64Array[] = [];
|
|
422
|
+
for (let p = 0; p < P; p++) {
|
|
423
|
+
const testKeys = zeroKeys.map((k) => ({ t: k.t, v: [...k.v], ...(k.curve !== undefined ? { curve: [...k.curve] } : {}) }));
|
|
424
|
+
if (p < K) testKeys[p]!.v[0] = 1;
|
|
425
|
+
else if (p < K + spans) testKeys[p - K]!.curve![1] = 1;
|
|
426
|
+
else testKeys[p - K - spans]!.curve![3] = 1;
|
|
427
|
+
const fp = evalTrack1D(testKeys, samples);
|
|
428
|
+
const col = new Float64Array(n);
|
|
429
|
+
for (let i = 0; i < n; i++) col[i] = fp[i]! - f0[i]!;
|
|
430
|
+
cols.push(col);
|
|
431
|
+
}
|
|
432
|
+
const d2Cols: Float64Array[] = cols.map((col) => {
|
|
433
|
+
const d = new Float64Array(Math.max(0, n - 2));
|
|
434
|
+
for (let i = 0; i < n - 2; i++) d[i] = col[i + 2]! - 2 * col[i + 1]! + col[i]!;
|
|
435
|
+
return d;
|
|
436
|
+
});
|
|
437
|
+
const activeFrames: number[][] = cols.map((col) => {
|
|
438
|
+
const idx: number[] = [];
|
|
439
|
+
for (let i = 0; i < n; i++) if (Math.abs(col[i]!) > 1e-9) idx.push(i);
|
|
440
|
+
return idx;
|
|
441
|
+
});
|
|
442
|
+
const activeD2: number[][] = d2Cols.map((dcol) => {
|
|
443
|
+
const idx: number[] = [];
|
|
444
|
+
for (let i = 0; i < dcol.length; i++) if (Math.abs(dcol[i]!) > 1e-9) idx.push(i);
|
|
445
|
+
return idx;
|
|
446
|
+
});
|
|
447
|
+
const x0 = new Float64Array(P);
|
|
448
|
+
for (let j = 0; j < K; j++) x0[j] = keys[j]!.v[0]!;
|
|
449
|
+
for (let j = 0; j < spans; j++) {
|
|
450
|
+
const c = keys[j]!.curve;
|
|
451
|
+
if (c !== undefined) {
|
|
452
|
+
x0[K + j] = c[1]!;
|
|
453
|
+
x0[K + spans + j] = c[3]!;
|
|
454
|
+
} else {
|
|
455
|
+
x0[K + j] = (2 * keys[j]!.v[0]! + keys[j + 1]!.v[0]!) / 3;
|
|
456
|
+
x0[K + spans + j] = (keys[j]!.v[0]! + 2 * keys[j + 1]!.v[0]!) / 3;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return { keys, samples, K, spans, P, hx1s, hx2s, f0, cols, d2Cols, activeFrames, activeD2, x0 };
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function solveMinPWL(terms: ReadonlyArray<{ a: number; b: number; w: number }>, lo: number, hi: number): number {
|
|
463
|
+
const cands = [lo, hi];
|
|
464
|
+
for (let i = 0; i < terms.length; i++) {
|
|
465
|
+
const t = terms[i]!;
|
|
466
|
+
if (Math.abs(t.a) > 1e-12) {
|
|
467
|
+
const r = -t.b / t.a;
|
|
468
|
+
if (r > lo && r < hi) cands.push(r);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
let bestX = lo;
|
|
472
|
+
let bestVal = Infinity;
|
|
473
|
+
for (let c = 0; c < cands.length; c++) {
|
|
474
|
+
const x = cands[c]!;
|
|
475
|
+
let val = 0;
|
|
476
|
+
for (let i = 0; i < terms.length; i++) {
|
|
477
|
+
const t = terms[i]!;
|
|
478
|
+
val += t.w * Math.abs(t.a * x + t.b);
|
|
479
|
+
}
|
|
480
|
+
if (val < bestVal) {
|
|
481
|
+
bestVal = val;
|
|
482
|
+
bestX = x;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return bestX;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function optimizeBasis(
|
|
489
|
+
basis: TrackBasis,
|
|
490
|
+
lb: readonly number[],
|
|
491
|
+
ub: readonly number[],
|
|
492
|
+
wSmooth = 1.0,
|
|
493
|
+
refY?: readonly number[],
|
|
494
|
+
refD2?: readonly number[],
|
|
495
|
+
): { keys: ReducedKey[]; y: number[] } {
|
|
496
|
+
const { keys, samples, K, spans, P, hx1s, hx2s, f0, cols, d2Cols, activeFrames, activeD2, x0 } = basis;
|
|
497
|
+
const n = samples.length;
|
|
498
|
+
const x = new Float64Array(x0);
|
|
499
|
+
const y = new Float64Array(n);
|
|
500
|
+
for (let i = 0; i < n; i++) {
|
|
501
|
+
let s = f0[i]!;
|
|
502
|
+
for (let p = 0; p < P; p++) s += cols[p]![i]! * x[p]!;
|
|
503
|
+
y[i] = s;
|
|
504
|
+
}
|
|
505
|
+
const seamWeight = Math.abs(y[n - 1]! - y[0]!) < 2.0 ? 20.0 : 0.0;
|
|
506
|
+
const seamDiff0 = y[n - 1]! - y[0]!;
|
|
507
|
+
const stepLimit = Math.max(5.0, Math.max(...ub.map((u, i) => Math.abs(u - lb[i]!))) * 5);
|
|
508
|
+
for (const pen of [2, 10, 50, 500, 10000, 100000]) {
|
|
509
|
+
for (let sweep = 0; sweep < 12; sweep++) {
|
|
510
|
+
for (let p = 0; p < P; p++) {
|
|
511
|
+
const col = cols[p]!;
|
|
512
|
+
const dcol = d2Cols[p]!;
|
|
513
|
+
const af = activeFrames[p]!;
|
|
514
|
+
const ad = activeD2[p]!;
|
|
515
|
+
const terms: Array<{ a: number; b: number; w: number }> = [];
|
|
516
|
+
for (let k = 0; k < ad.length; k++) {
|
|
517
|
+
const i = ad[k]!;
|
|
518
|
+
const targetD2 = refD2 !== undefined ? refD2[i]! : 0;
|
|
519
|
+
const curD2 = y[i + 2]! - 2 * y[i + 1]! + y[i]! - targetD2;
|
|
520
|
+
terms.push({ a: dcol[i]!, b: curD2, w: wSmooth / Math.max(1, n - 2) });
|
|
521
|
+
}
|
|
522
|
+
if (seamWeight > 0 && (Math.abs(col[0]!) > 1e-9 || Math.abs(col[n - 1]!) > 1e-9)) {
|
|
523
|
+
const aSeam = col[n - 1]! - col[0]!;
|
|
524
|
+
const bSeam = y[n - 1]! - y[0]! - seamDiff0;
|
|
525
|
+
terms.push({ a: aSeam, b: bSeam, w: seamWeight });
|
|
526
|
+
}
|
|
527
|
+
for (let k = 0; k < af.length; k++) {
|
|
528
|
+
const i = af[k]!;
|
|
529
|
+
const ci = col[i]!;
|
|
530
|
+
const mid = 0.5 * (lb[i]! + ub[i]!);
|
|
531
|
+
const half = Math.max(1e-6, 0.5 * (ub[i]! - lb[i]!));
|
|
532
|
+
const bMid = y[i]! - mid;
|
|
533
|
+
terms.push({ a: ci, b: bMid - half, w: pen * 0.5 });
|
|
534
|
+
terms.push({ a: ci, b: bMid + half, w: pen * 0.5 });
|
|
535
|
+
if (refY !== undefined) {
|
|
536
|
+
terms.push({ a: ci, b: y[i]! - refY[i]!, w: 1.0 / n });
|
|
537
|
+
} else {
|
|
538
|
+
terms.push({ a: ci, b: bMid, w: 1e-5 / n });
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
const dx = solveMinPWL(terms, -stepLimit, stepLimit);
|
|
542
|
+
if (Math.abs(dx) > 1e-9) {
|
|
543
|
+
x[p]! += dx;
|
|
544
|
+
for (let k = 0; k < af.length; k++) y[af[k]!]! += col[af[k]!]! * dx;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
const outKeys: ReducedKey[] = keys.map((k, j) => {
|
|
550
|
+
const v0 = r3(x[j]!);
|
|
551
|
+
if (j + 1 < K) {
|
|
552
|
+
const cy1 = r3(x[K + j]!);
|
|
553
|
+
const cy2 = r3(x[K + spans + j]!);
|
|
554
|
+
if (k.curve === undefined) {
|
|
555
|
+
const v1 = r3(x[j + 1]!);
|
|
556
|
+
const lin1 = r3((2 * v0 + v1) / 3);
|
|
557
|
+
const lin2 = r3((v0 + 2 * v1) / 3);
|
|
558
|
+
if (Math.abs(cy1 - lin1) <= 0.001 && Math.abs(cy2 - lin2) <= 0.001) return { t: k.t, v: [v0] };
|
|
559
|
+
}
|
|
560
|
+
return { t: k.t, v: [v0], curve: [hx1s[j]!, cy1, hx2s[j]!, cy2] };
|
|
561
|
+
}
|
|
562
|
+
return { t: k.t, v: [v0] };
|
|
563
|
+
});
|
|
564
|
+
return { keys: outKeys, y: evalTrack1D(outKeys, samples) };
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function msdDeg(arr: readonly number[]): number {
|
|
568
|
+
if (arr.length < 3) return 0;
|
|
569
|
+
let s = 0;
|
|
570
|
+
for (let i = 1; i < arr.length - 1; i++) s += Math.abs(wrap180(arr[i + 1]! - arr[i]!) - wrap180(arr[i]! - arr[i - 1]!));
|
|
571
|
+
return s / (arr.length - 2);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function d2Of(arr: readonly number[]): number[] {
|
|
575
|
+
const res: number[] = [];
|
|
576
|
+
for (let i = 0; i < arr.length - 2; i++) res.push(arr[i + 2]! - 2 * arr[i + 1]! + arr[i]!);
|
|
577
|
+
return res;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function solveLimbPreservingJitter(
|
|
581
|
+
basis: TrackBasis,
|
|
582
|
+
yOld: readonly number[],
|
|
583
|
+
lb: readonly number[],
|
|
584
|
+
ub: readonly number[],
|
|
585
|
+
): { keys: ReducedKey[]; y: number[] } {
|
|
586
|
+
const jOld = msdDeg(yOld);
|
|
587
|
+
const baseD2 = d2Of(yOld);
|
|
588
|
+
let scale = 1.0;
|
|
589
|
+
let sm = optimizeBasis(basis, lb, ub, 4.0, yOld, baseD2);
|
|
590
|
+
for (let sIter = 0; sIter < 2; sIter++) {
|
|
591
|
+
const jNew = msdDeg(sm.y);
|
|
592
|
+
if (Math.abs(jNew - jOld) <= 0.02 || jNew < 1e-6) break;
|
|
593
|
+
scale *= jOld / jNew;
|
|
594
|
+
sm = optimizeBasis(basis, lb, ub, 4.0, yOld, baseD2.map((d) => d * scale));
|
|
595
|
+
}
|
|
596
|
+
return sm;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Smooths the reduced trunk and head rotate tracks within the stated world-aim
|
|
601
|
+
* tolerance (#116): fitTrack's per-span least-squares handles leave C0 slope
|
|
602
|
+
* breaks at knots pinned to the raw frame values, which across a parent→child
|
|
603
|
+
* trunk chain (`chest` → `head`) can amplify local second-difference jitter
|
|
604
|
+
* above the sequence's own. Optimizing knot values and handle y-coordinates
|
|
605
|
+
* jointly within the ±tolerance world-aim tube removes those C0 breaks while
|
|
606
|
+
* keeping any limb chains hanging from the trunk within 0.04 of their landing
|
|
607
|
+
* and jitter.
|
|
608
|
+
*/
|
|
609
|
+
export function smoothTrunk(
|
|
610
|
+
times: readonly number[],
|
|
611
|
+
bones: readonly ReduceBone[],
|
|
612
|
+
tolerance: number,
|
|
613
|
+
out: Reduced,
|
|
614
|
+
trunkBones: ReadonlySet<string>,
|
|
615
|
+
): void {
|
|
616
|
+
const n = times.length;
|
|
617
|
+
if (n < 4 || trunkBones.size === 0) return;
|
|
618
|
+
const samples = samplesOf(times);
|
|
619
|
+
const byName = new Map(bones.map((b) => [b.bone, b]));
|
|
620
|
+
const roots = [...trunkBones].filter((b) => {
|
|
621
|
+
const info = byName.get(b);
|
|
622
|
+
return info !== undefined && (info.above === null || !trunkBones.has(info.above));
|
|
623
|
+
});
|
|
624
|
+
const tolInner = 0.985 * tolerance;
|
|
625
|
+
|
|
626
|
+
for (const root of roots) {
|
|
627
|
+
const rootKeys = out.rotate.get(root);
|
|
628
|
+
if (rootKeys === undefined || rootKeys.length <= 2) continue;
|
|
629
|
+
const headBones = [...trunkBones].filter((b) => byName.get(b)?.above === root && (out.rotate.get(b)?.length ?? 0) > 2);
|
|
630
|
+
if (headBones.length === 0) continue;
|
|
631
|
+
const arm0Bones = bones.filter((b) => !trunkBones.has(b.bone) && b.above === root && (out.rotate.get(b.bone)?.length ?? 0) > 2).map((b) => b.bone);
|
|
632
|
+
const arm0Set = new Set(arm0Bones);
|
|
633
|
+
const arm1Bones = bones.filter((b) => !trunkBones.has(b.bone) && b.above !== null && arm0Set.has(b.above) && (out.rotate.get(b.bone)?.length ?? 0) > 2).map((b) => b.bone);
|
|
634
|
+
const allManaged = [root, ...headBones, ...arm0Bones, ...arm1Bones];
|
|
635
|
+
|
|
636
|
+
const basisOf = new Map<string, TrackBasis>();
|
|
637
|
+
const yOldOf = new Map<string, number[]>();
|
|
638
|
+
for (const b of allManaged) {
|
|
639
|
+
const k = out.rotate.get(b)!;
|
|
640
|
+
basisOf.set(b, buildBasis(k, samples));
|
|
641
|
+
yOldOf.set(b, evalTrack1D(k, samples));
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const rootInfo = byName.get(root)!;
|
|
645
|
+
const eAboveRoot = rootInfo.above === null ? new Array<number>(n).fill(0) : out.errors.get(rootInfo.above)!;
|
|
646
|
+
const targetRoot = rootInfo.dense.map((d, i) => d - eAboveRoot[i]!);
|
|
647
|
+
const yRootOld = yOldOf.get(root)!;
|
|
648
|
+
|
|
649
|
+
const rootLb = targetRoot.map((t) => t - tolInner);
|
|
650
|
+
const rootUb = targetRoot.map((t) => t + tolInner);
|
|
651
|
+
let smRoot = optimizeBasis(basisOf.get(root)!, rootLb, rootUb);
|
|
652
|
+
let eRoot = smRoot.y.map((v, i) => v - targetRoot[i]!);
|
|
653
|
+
|
|
654
|
+
const smSub = new Map<string, { keys: ReducedKey[]; y: number[] }>();
|
|
655
|
+
const eSub = new Map<string, number[]>();
|
|
656
|
+
|
|
657
|
+
const tightenAround = (i: number, excess: number) => {
|
|
658
|
+
for (let k = Math.max(0, i - 2); k <= Math.min(n - 1, i + 2); k++) {
|
|
659
|
+
const w = k === i ? 1.0 : 0.7;
|
|
660
|
+
if (smRoot.y[k]! > yRootOld[k]!) rootUb[k] = Math.min(rootUb[k]!, Math.max(yRootOld[k]!, smRoot.y[k]! - excess * w));
|
|
661
|
+
else rootLb[k] = Math.max(rootLb[k]!, Math.min(yRootOld[k]!, smRoot.y[k]! + excess * w));
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
const solveChildren = () => {
|
|
666
|
+
eSub.set(root, eRoot);
|
|
667
|
+
for (const h of headBones) {
|
|
668
|
+
const dH = byName.get(h)!.dense;
|
|
669
|
+
const targetH = dH.map((d, i) => d - eRoot[i]!);
|
|
670
|
+
const smH = optimizeBasis(basisOf.get(h)!, targetH.map((t) => t - tolInner), targetH.map((t) => t + tolInner));
|
|
671
|
+
smSub.set(h, smH);
|
|
672
|
+
eSub.set(h, smH.y.map((v, i) => v - targetH[i]!));
|
|
673
|
+
}
|
|
674
|
+
for (const b0 of arm0Bones) {
|
|
675
|
+
const yOld = yOldOf.get(b0)!;
|
|
676
|
+
const wOld = Math.max(...out.errors.get(b0)!.map(Math.abs));
|
|
677
|
+
const cap = Math.min(0.98 * tolerance, wOld + 0.035 * tolerance);
|
|
678
|
+
const targetB = byName.get(b0)!.dense.map((d, i) => d - eRoot[i]!);
|
|
679
|
+
const smB = solveLimbPreservingJitter(basisOf.get(b0)!, yOld, targetB.map((t) => t - cap), targetB.map((t) => t + cap));
|
|
680
|
+
smSub.set(b0, smB);
|
|
681
|
+
eSub.set(b0, smB.y.map((v, i) => v - targetB[i]!));
|
|
682
|
+
}
|
|
683
|
+
for (const b1 of arm1Bones) {
|
|
684
|
+
const up = byName.get(b1)!.above!;
|
|
685
|
+
const eUp = eSub.get(up)!;
|
|
686
|
+
const yOld = yOldOf.get(b1)!;
|
|
687
|
+
const wOld = Math.max(...out.errors.get(b1)!.map(Math.abs));
|
|
688
|
+
const cap = Math.min(0.98 * tolerance, wOld + 0.035 * tolerance);
|
|
689
|
+
const targetB = byName.get(b1)!.dense.map((d, i) => d - eUp[i]!);
|
|
690
|
+
const smB = solveLimbPreservingJitter(basisOf.get(b1)!, yOld, targetB.map((t) => t - cap), targetB.map((t) => t + cap));
|
|
691
|
+
smSub.set(b1, smB);
|
|
692
|
+
eSub.set(b1, smB.y.map((v, i) => v - targetB[i]!));
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
for (let round = 0; round < 5; round++) {
|
|
697
|
+
solveChildren();
|
|
698
|
+
let anyTightened = false;
|
|
699
|
+
for (let i = 0; i < n; i++) {
|
|
700
|
+
for (const h of headBones) {
|
|
701
|
+
const eh = eSub.get(h)![i]!;
|
|
702
|
+
if (Math.abs(eh) > 0.99 * tolerance) {
|
|
703
|
+
tightenAround(i, Math.abs(eh) - 0.975 * tolerance);
|
|
704
|
+
anyTightened = true;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
for (const b of [...arm0Bones, ...arm1Bones]) {
|
|
708
|
+
const wOld = Math.max(...out.errors.get(b)!.map(Math.abs));
|
|
709
|
+
const cap = Math.min(0.985 * tolerance, wOld + 0.04 * tolerance);
|
|
710
|
+
const eb = eSub.get(b)![i]!;
|
|
711
|
+
if (Math.abs(eb) > cap) {
|
|
712
|
+
tightenAround(i, Math.abs(eb) - (cap - 0.01 * tolerance));
|
|
713
|
+
anyTightened = true;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
if (!anyTightened) break;
|
|
718
|
+
smRoot = optimizeBasis(basisOf.get(root)!, rootLb, rootUb);
|
|
719
|
+
eRoot = smRoot.y.map((v, i) => v - targetRoot[i]!);
|
|
720
|
+
if (round === 4) solveChildren();
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const updateBone = (b: string, keys: ReducedKey[], e: number[]) => {
|
|
724
|
+
out.rotate.set(b, keys);
|
|
725
|
+
out.errors.set(b, e);
|
|
726
|
+
const worst = worstOf(e.map((_, i) => i), (i) => Math.abs(e[i]!))!;
|
|
727
|
+
const rowIdx = out.rows.findIndex((r) => r.kind === 'angle' && r.bone === b);
|
|
728
|
+
if (rowIdx >= 0) {
|
|
729
|
+
out.rows[rowIdx] = { bone: b, kind: 'angle', before: n, after: keys.length, worst: worst.worst, frame: worst.frame };
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
updateBone(root, smRoot.keys, eRoot);
|
|
734
|
+
for (const b of [...headBones, ...arm0Bones, ...arm1Bones]) {
|
|
735
|
+
updateBone(b, smSub.get(b)!.keys, eSub.get(b)!);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
|
|
382
741
|
/** The times the poser samples a track at: the running sum of the frame steps (verify.ts `poseFrames`). */
|
|
383
742
|
export function samplesOf(times: readonly number[]): number[] {
|
|
384
743
|
const samples: number[] = [times[0]!];
|
package/src/run.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { attachmentBones, baseMotionOf, buildInto, rigcVersion, folds, gate, rea
|
|
|
17
17
|
import { artRangeLine, chainsMoving, raisedLine, RAISED_WITHIN, reachLine, switchLine, uncoveredFolds } from './switch.ts';
|
|
18
18
|
import { landed, measureFeet, measureLanding, measurePosture, measureShear, poseFrames, postureLines } from './verify.ts';
|
|
19
19
|
import { legsOf, PLANT_SWITCH, STANCE_BOUNDS } from './legs.ts';
|
|
20
|
-
import { M2_IDENTITY, mulM2, reduceKeys, reduceScale, reduceUnderScale, SCALE_REDUCE, setupLocal, type M2, type ReduceBone, type Reduced, type ReduceRow, type UnderBone } from './reduce.ts';
|
|
20
|
+
import { M2_IDENTITY, mulM2, reduceKeys, reduceScale, reduceUnderScale, SCALE_REDUCE, setupLocal, smoothTrunk, type M2, type ReduceBone, type Reduced, type ReduceRow, type UnderBone } from './reduce.ts';
|
|
21
21
|
import type { BlendHold, Closure, Floor, LegScale, Lowered, Side, Stance, SupportScaleRun } from './legs.ts';
|
|
22
22
|
import { faceLine } from './face.ts';
|
|
23
23
|
|
|
@@ -444,6 +444,15 @@ function reduceTracks(r: ReturnType<typeof retarget>, map: JointMap, rest: Rest,
|
|
|
444
444
|
if (hipTrack !== undefined && out.hip !== null) hipTrack.keys = out.hip;
|
|
445
445
|
if (scalex.size === 0) return out;
|
|
446
446
|
|
|
447
|
+
// #116: smooth the trunk-and-head chain within the stated tolerance while keeping limb landings and jitter within 0.04.
|
|
448
|
+
const trunkBones = new Set(
|
|
449
|
+
map.chains
|
|
450
|
+
.filter((c) => c.bones.length === 1 && !(c.joints.includes('r_hip') && c.joints.includes('l_hip')))
|
|
451
|
+
.map((c) => c.bones[0]!),
|
|
452
|
+
);
|
|
453
|
+
smoothTrunk(r.times, bones, tolerance, out, trunkBones);
|
|
454
|
+
for (const [bone, keys] of out.rotate) rotate.get(bone)!.keys = keys;
|
|
455
|
+
|
|
447
456
|
// #94: the scales of the bones the key formula holds for, then the bones under a scaled bone, parents first.
|
|
448
457
|
const rows = new Map<string, ReduceRow>();
|
|
449
458
|
for (const row of out.rows) if (row.kind === 'angle') rows.set(`rotate ${row.bone}`, row);
|