@ikijs/editor 0.24.1 → 0.25.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 +16 -14
- package/dist/index.d.mts +78 -3
- package/dist/index.d.ts +78 -3
- package/dist/index.js +437 -125
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +435 -123
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1476,8 +1476,8 @@ function foregroundSpan(mask, width, height, rowLo, rowHi) {
|
|
|
1476
1476
|
}
|
|
1477
1477
|
return { left, right };
|
|
1478
1478
|
}
|
|
1479
|
-
function headHalfOf(
|
|
1480
|
-
return (
|
|
1479
|
+
function headHalfOf(span2) {
|
|
1480
|
+
return (span2.right - span2.left + 1) / 2;
|
|
1481
1481
|
}
|
|
1482
1482
|
var DARK_SHARE = 0.55;
|
|
1483
1483
|
function jawRowsOf(rgba, canvasW, bbox, rowLeft, rowRight) {
|
|
@@ -1487,9 +1487,9 @@ function jawRowsOf(rgba, canvasW, bbox, rowLeft, rowRight) {
|
|
|
1487
1487
|
let widest = bbox.y;
|
|
1488
1488
|
let widestSpan = -1;
|
|
1489
1489
|
for (let y = bbox.y; y < bbox.y + bbox.h; y++) {
|
|
1490
|
-
const
|
|
1491
|
-
if (
|
|
1492
|
-
widestSpan =
|
|
1490
|
+
const span2 = rowRight[y] - rowLeft[y];
|
|
1491
|
+
if (span2 >= widestSpan) {
|
|
1492
|
+
widestSpan = span2;
|
|
1493
1493
|
widest = y;
|
|
1494
1494
|
}
|
|
1495
1495
|
for (let x = bbox.x; x < bbox.x + bbox.w; x++) {
|
|
@@ -1658,14 +1658,14 @@ function createLayerSetMeasurer(canvas) {
|
|
|
1658
1658
|
}
|
|
1659
1659
|
function finish() {
|
|
1660
1660
|
const eyeRow = eyeBandRowOf(layerInputs);
|
|
1661
|
-
const
|
|
1661
|
+
const span2 = foregroundSpan(
|
|
1662
1662
|
opaque,
|
|
1663
1663
|
canvasW,
|
|
1664
1664
|
canvasH,
|
|
1665
1665
|
eyeRow - HEAD_BAND,
|
|
1666
1666
|
eyeRow + HEAD_BAND
|
|
1667
1667
|
);
|
|
1668
|
-
const headHalfWidth =
|
|
1668
|
+
const headHalfWidth = span2.right > span2.left ? headHalfOf(span2) : void 0;
|
|
1669
1669
|
const headHalfWidthApplied = headHalfWidth !== void 0 && headHalfWidth > facePlateHalfOf(layerInputs);
|
|
1670
1670
|
let headEdges;
|
|
1671
1671
|
if (headHalfWidthApplied) {
|
|
@@ -1693,9 +1693,9 @@ function createLayerSetMeasurer(canvas) {
|
|
|
1693
1693
|
const centre = iris.bbox.x + iris.bbox.w / 2;
|
|
1694
1694
|
const otherX = other.bbox.x + other.bbox.w / 2 - half;
|
|
1695
1695
|
const c = side < 0 ? Math.ceil(centre) - 1 : Math.floor(centre);
|
|
1696
|
-
const
|
|
1697
|
-
if (
|
|
1698
|
-
const minRun = STRAND_MIN_RUN_FRACTION * (
|
|
1696
|
+
const span3 = rowSpansByRole.find((s) => s.role === iris.role);
|
|
1697
|
+
if (span3.rowRight[r] < span3.rowLeft[r]) return void 0;
|
|
1698
|
+
const minRun = STRAND_MIN_RUN_FRACTION * (span3.rowRight[r] + 1 - span3.rowLeft[r]);
|
|
1699
1699
|
const strand = new Uint8Array(canvasW);
|
|
1700
1700
|
const runs = r >= hairTop && r < hairTop + hairRuns.length ? hairRuns[r - hairTop] : [];
|
|
1701
1701
|
for (let k = 0; k < runs.length; k += 2) {
|
|
@@ -1720,8 +1720,8 @@ function createLayerSetMeasurer(canvas) {
|
|
|
1720
1720
|
while (strandAt(x)) x += side;
|
|
1721
1721
|
return {
|
|
1722
1722
|
y: canvasH / 2 - (r + 0.5),
|
|
1723
|
-
irisOuter: (side < 0 ?
|
|
1724
|
-
irisInner: (side < 0 ?
|
|
1723
|
+
irisOuter: (side < 0 ? span3.rowLeft[r] : span3.rowRight[r] + 1) - half,
|
|
1724
|
+
irisInner: (side < 0 ? span3.rowRight[r] + 1 : span3.rowLeft[r]) - half,
|
|
1725
1725
|
runOuter: outerBoundary(x - side),
|
|
1726
1726
|
runFace
|
|
1727
1727
|
};
|
|
@@ -2125,6 +2125,12 @@ var DEFAULT_TURN_TARGETS = Object.freeze({
|
|
|
2125
2125
|
farEyeRatio: round2(TURN.eyeFarScale / TURN.eyeNearScale),
|
|
2126
2126
|
silhouetteRatio: 1
|
|
2127
2127
|
});
|
|
2128
|
+
var PatchError = class extends Error {
|
|
2129
|
+
constructor(message) {
|
|
2130
|
+
super(message);
|
|
2131
|
+
this.name = "PatchError";
|
|
2132
|
+
}
|
|
2133
|
+
};
|
|
2128
2134
|
|
|
2129
2135
|
// src/auto-rig/arms.ts
|
|
2130
2136
|
import {
|
|
@@ -2199,15 +2205,16 @@ function gridMesh(cols, rows) {
|
|
|
2199
2205
|
return { vertices, uvs, indices };
|
|
2200
2206
|
}
|
|
2201
2207
|
function columnMesh(b, grid) {
|
|
2202
|
-
const { xs } = grid;
|
|
2203
|
-
const rows =
|
|
2208
|
+
const { xs, ys } = grid;
|
|
2209
|
+
const rows = ys.length - 1;
|
|
2204
2210
|
const vertices = [];
|
|
2205
2211
|
const uvs = [];
|
|
2206
2212
|
for (let r = 0; r <= rows; r++) {
|
|
2213
|
+
const v = (ys[0] - ys[r]) / (ys[0] - ys[rows]);
|
|
2207
2214
|
for (const x of xs) {
|
|
2208
2215
|
const u = (x - b.x0) / bw(b);
|
|
2209
|
-
vertices.push(roundTo(u - 0.5, 1e-5), roundTo(0.5 -
|
|
2210
|
-
uvs.push(roundTo(u, 1e-5), roundTo(
|
|
2216
|
+
vertices.push(roundTo(u - 0.5, 1e-5), roundTo(0.5 - v, 1e-5));
|
|
2217
|
+
uvs.push(roundTo(u, 1e-5), roundTo(v, 1e-5));
|
|
2211
2218
|
}
|
|
2212
2219
|
}
|
|
2213
2220
|
const n = xs.length;
|
|
@@ -2756,7 +2763,7 @@ var ARM_IDS = {
|
|
|
2756
2763
|
};
|
|
2757
2764
|
function endPivot(layer, end) {
|
|
2758
2765
|
const { bbox, canvasW, canvasH, rowRuns } = layer;
|
|
2759
|
-
const
|
|
2766
|
+
const span2 = runSpan(layer);
|
|
2760
2767
|
const from = (k) => end === "top" ? k : bbox.h - 1 - k;
|
|
2761
2768
|
const painted = rowRuns?.flatMap((runs, k) => runs.length > 0 ? [k] : []) ?? [];
|
|
2762
2769
|
const firstK = end === "top" ? painted[0] ?? 0 : painted.at(-1) ?? bbox.h - 1;
|
|
@@ -2764,13 +2771,13 @@ function endPivot(layer, end) {
|
|
|
2764
2771
|
const hi = Math.max(lo, Math.floor(CAP_ROWS[1] * bbox.h));
|
|
2765
2772
|
const widths = [];
|
|
2766
2773
|
for (let k = lo; k <= hi; k++) {
|
|
2767
|
-
const [s2, e2] =
|
|
2774
|
+
const [s2, e2] = span2(from(k));
|
|
2768
2775
|
widths.push(e2 - s2);
|
|
2769
2776
|
}
|
|
2770
2777
|
widths.sort((p, q) => p - q);
|
|
2771
2778
|
const radius = widths[widths.length >> 1] / 2;
|
|
2772
2779
|
const ks = end === "top" ? firstK + radius : firstK - radius;
|
|
2773
|
-
const [s, e] =
|
|
2780
|
+
const [s, e] = span2(Math.round(ks));
|
|
2774
2781
|
return {
|
|
2775
2782
|
point: {
|
|
2776
2783
|
x: roundTo((s + e) / 2 - canvasW / 2, 0.01),
|
|
@@ -2795,9 +2802,9 @@ function runSpan(layer) {
|
|
|
2795
2802
|
function armGeometry(layer, bodyAxisX) {
|
|
2796
2803
|
const { bbox, canvasW, canvasH, rowRuns } = layer;
|
|
2797
2804
|
const b = boxOfLayer(layer);
|
|
2798
|
-
const
|
|
2805
|
+
const span2 = runSpan(layer);
|
|
2799
2806
|
const centreX = (k) => {
|
|
2800
|
-
const [s2, e2] =
|
|
2807
|
+
const [s2, e2] = span2(k);
|
|
2801
2808
|
return (s2 + e2) / 2 - canvasW / 2;
|
|
2802
2809
|
};
|
|
2803
2810
|
const rowY = (k) => canvasH / 2 - (bbox.y + k + 0.5);
|
|
@@ -2811,7 +2818,7 @@ function armGeometry(layer, bodyAxisX) {
|
|
|
2811
2818
|
);
|
|
2812
2819
|
}
|
|
2813
2820
|
const elbow = { x: roundTo(centreX(ke), 0.01), y: roundTo(rowY(ke), 0.01) };
|
|
2814
|
-
const [s, e] =
|
|
2821
|
+
const [s, e] = span2(ke);
|
|
2815
2822
|
const capRadius = Math.min(
|
|
2816
2823
|
(e - s) / 2 + 1,
|
|
2817
2824
|
elbow.x - b.x0,
|
|
@@ -2821,7 +2828,7 @@ function armGeometry(layer, bodyAxisX) {
|
|
|
2821
2828
|
const inside = (x, y) => {
|
|
2822
2829
|
const k = Math.floor(y) - bbox.y;
|
|
2823
2830
|
if (k < 0 || k >= bbox.h) return false;
|
|
2824
|
-
const [rs, re] =
|
|
2831
|
+
const [rs, re] = span2(k);
|
|
2825
2832
|
return rs < x && x < re;
|
|
2826
2833
|
};
|
|
2827
2834
|
const capRays = Array.from({ length: CAP_SEGMENTS + 1 }, (_, i) => {
|
|
@@ -3031,14 +3038,25 @@ function forearmPoseDeformer(role, arm) {
|
|
|
3031
3038
|
|
|
3032
3039
|
// src/auto-rig/mouth.ts
|
|
3033
3040
|
import {
|
|
3034
|
-
StandardParameter as
|
|
3041
|
+
StandardParameter as P6
|
|
3035
3042
|
} from "@ikijs/format";
|
|
3036
3043
|
|
|
3037
3044
|
// src/auto-rig/roles.ts
|
|
3045
|
+
import { StandardParameter as P5 } from "@ikijs/format";
|
|
3038
3046
|
var LIP_ROLES = ["mouth_inner", "lip_lower", "lip_upper"];
|
|
3039
3047
|
var isLipRole = (role) => LIP_ROLES.includes(role);
|
|
3040
3048
|
var LIP_INSIDE_ROLES = ["mouth_tongue", "mouth_teeth"];
|
|
3041
3049
|
var isLipInsideRole = (role) => LIP_INSIDE_ROLES.includes(role);
|
|
3050
|
+
var SHUT_BY = new Map([
|
|
3051
|
+
["eye_L", P5.EyeOpenLeft],
|
|
3052
|
+
["lash_lower_L", P5.EyeOpenLeft],
|
|
3053
|
+
["eye_R", P5.EyeOpenRight],
|
|
3054
|
+
["lash_lower_R", P5.EyeOpenRight],
|
|
3055
|
+
...[...LIP_ROLES, ...LIP_INSIDE_ROLES].map((id) => [
|
|
3056
|
+
id,
|
|
3057
|
+
P5.MouthOpen
|
|
3058
|
+
])
|
|
3059
|
+
]);
|
|
3042
3060
|
var ROLE_TABLE = [
|
|
3043
3061
|
{ role: "hair_back", family: "hair_back" },
|
|
3044
3062
|
{ role: "body", family: "body" },
|
|
@@ -3383,7 +3401,7 @@ function columnOfKnot(knot, opening) {
|
|
|
3383
3401
|
function mouthFoldWarp(role, mesh, xs, box, opening) {
|
|
3384
3402
|
const fold = mouthFold(role, opening);
|
|
3385
3403
|
return localWarp(
|
|
3386
|
-
|
|
3404
|
+
P6.MouthOpen,
|
|
3387
3405
|
mesh,
|
|
3388
3406
|
box,
|
|
3389
3407
|
[0, 1],
|
|
@@ -3392,13 +3410,13 @@ function mouthFoldWarp(role, mesh, xs, box, opening) {
|
|
|
3392
3410
|
}
|
|
3393
3411
|
function mouthRestShift(byRole) {
|
|
3394
3412
|
const rig = buildMouthRig(byRole);
|
|
3395
|
-
const
|
|
3413
|
+
const fields2 = /* @__PURE__ */ new Map();
|
|
3396
3414
|
for (const role of ["lip_upper", "lip_lower"]) {
|
|
3397
3415
|
const box = boxOfLayer(byRole.get(role));
|
|
3398
3416
|
const { mesh, xs } = mouthMesh(box, rig.knots);
|
|
3399
3417
|
const warp = mouthFoldWarp(role, mesh, xs, box, rig.opening);
|
|
3400
3418
|
const offsets = warp.keyforms[0].offsets;
|
|
3401
|
-
|
|
3419
|
+
fields2.set(role, {
|
|
3402
3420
|
box,
|
|
3403
3421
|
xs,
|
|
3404
3422
|
rows: offsets.length / 2 / xs.length - 1,
|
|
@@ -3407,7 +3425,7 @@ function mouthRestShift(byRole) {
|
|
|
3407
3425
|
}
|
|
3408
3426
|
const { canvasW, canvasH } = byRole.get("lip_upper");
|
|
3409
3427
|
return (role, col, row) => {
|
|
3410
|
-
const { box, xs, rows, dy } =
|
|
3428
|
+
const { box, xs, rows, dy } = fields2.get(role);
|
|
3411
3429
|
const x = col + 0.5 - canvasW / 2;
|
|
3412
3430
|
const y = canvasH / 2 - (row + 0.5);
|
|
3413
3431
|
if (x < box.x0 || x > box.x1 || y < box.y0 || y > box.y1) return void 0;
|
|
@@ -3498,36 +3516,36 @@ function mouthAnchor(byRole) {
|
|
|
3498
3516
|
// src/auto-rig/generate.ts
|
|
3499
3517
|
import {
|
|
3500
3518
|
IKI_FORMAT_VERSION as IKI_FORMAT_VERSION2,
|
|
3501
|
-
StandardParameter as
|
|
3519
|
+
StandardParameter as P10,
|
|
3502
3520
|
parseIkiModel as parseIkiModel3
|
|
3503
3521
|
} from "@ikijs/format";
|
|
3504
3522
|
|
|
3505
3523
|
// src/auto-rig/animations.ts
|
|
3506
3524
|
import {
|
|
3507
|
-
StandardParameter as
|
|
3525
|
+
StandardParameter as P7
|
|
3508
3526
|
} from "@ikijs/format";
|
|
3509
3527
|
function browsY(y) {
|
|
3510
3528
|
return [
|
|
3511
|
-
{ parameter:
|
|
3512
|
-
{ parameter:
|
|
3529
|
+
{ parameter: P7.BrowLeftY, value: y, blend: "add" },
|
|
3530
|
+
{ parameter: P7.BrowRightY, value: y, blend: "add" }
|
|
3513
3531
|
];
|
|
3514
3532
|
}
|
|
3515
3533
|
function browsTilt(innerDown) {
|
|
3516
3534
|
return [
|
|
3517
|
-
{ parameter:
|
|
3518
|
-
{ parameter:
|
|
3535
|
+
{ parameter: P7.BrowLeftAngle, value: innerDown, blend: "add" },
|
|
3536
|
+
{ parameter: P7.BrowRightAngle, value: -innerDown, blend: "add" }
|
|
3519
3537
|
];
|
|
3520
3538
|
}
|
|
3521
3539
|
function cheek(value) {
|
|
3522
|
-
return { parameter:
|
|
3540
|
+
return { parameter: P7.Cheek, value, blend: "add" };
|
|
3523
3541
|
}
|
|
3524
|
-
var BROWS = [
|
|
3542
|
+
var BROWS = [P7.BrowLeftY, P7.BrowRightY, P7.BrowLeftAngle, P7.BrowRightAngle];
|
|
3525
3543
|
var EXPRESSIONS = [
|
|
3526
3544
|
{
|
|
3527
3545
|
id: "smile",
|
|
3528
3546
|
description: "Smiling and pleased: warm, friendly, content. For greetings, thanks, agreement and gentle happiness.",
|
|
3529
3547
|
parameters: [
|
|
3530
|
-
{ parameter:
|
|
3548
|
+
{ parameter: P7.MouthForm, value: 1, blend: "add" },
|
|
3531
3549
|
...browsY(0.25),
|
|
3532
3550
|
cheek(0.4)
|
|
3533
3551
|
]
|
|
@@ -3536,10 +3554,10 @@ var EXPRESSIONS = [
|
|
|
3536
3554
|
id: "laugh",
|
|
3537
3555
|
description: "Laughing, eyes shut and mouth open: delighted, amused. For jokes, playfulness and big joy.",
|
|
3538
3556
|
parameters: [
|
|
3539
|
-
{ parameter:
|
|
3540
|
-
{ parameter:
|
|
3541
|
-
{ parameter:
|
|
3542
|
-
{ parameter:
|
|
3557
|
+
{ parameter: P7.EyeOpenLeft, value: 0, blend: "multiply" },
|
|
3558
|
+
{ parameter: P7.EyeOpenRight, value: 0, blend: "multiply" },
|
|
3559
|
+
{ parameter: P7.MouthForm, value: 1, blend: "add" },
|
|
3560
|
+
{ parameter: P7.MouthOpen, value: 0.8, blend: "overwrite" },
|
|
3543
3561
|
...browsY(0.4),
|
|
3544
3562
|
cheek(0.65)
|
|
3545
3563
|
]
|
|
@@ -3551,7 +3569,7 @@ var EXPRESSIONS = [
|
|
|
3551
3569
|
parameters: [
|
|
3552
3570
|
...browsY(-0.5),
|
|
3553
3571
|
...browsTilt(0.8),
|
|
3554
|
-
{ parameter:
|
|
3572
|
+
{ parameter: P7.MouthForm, value: -0.9, blend: "add" }
|
|
3555
3573
|
]
|
|
3556
3574
|
},
|
|
3557
3575
|
{
|
|
@@ -3561,7 +3579,7 @@ var EXPRESSIONS = [
|
|
|
3561
3579
|
parameters: [
|
|
3562
3580
|
...browsY(0.35),
|
|
3563
3581
|
...browsTilt(-0.8),
|
|
3564
|
-
{ parameter:
|
|
3582
|
+
{ parameter: P7.MouthForm, value: -0.8, blend: "add" }
|
|
3565
3583
|
]
|
|
3566
3584
|
},
|
|
3567
3585
|
{
|
|
@@ -3569,18 +3587,18 @@ var EXPRESSIONS = [
|
|
|
3569
3587
|
description: "Surprised, mouth dropped open: startled, amazed. For shock, sudden news or disbelief.",
|
|
3570
3588
|
parameters: [
|
|
3571
3589
|
...browsY(0.8),
|
|
3572
|
-
{ parameter:
|
|
3590
|
+
{ parameter: P7.MouthOpen, value: 0.9, blend: "overwrite" }
|
|
3573
3591
|
]
|
|
3574
3592
|
},
|
|
3575
3593
|
{
|
|
3576
3594
|
id: "shy",
|
|
3577
3595
|
description: "Shy or embarrassed: bashful, flustered. For being praised, teased or caught off guard.",
|
|
3578
|
-
needs: [
|
|
3596
|
+
needs: [P7.Cheek],
|
|
3579
3597
|
parameters: [
|
|
3580
3598
|
cheek(1),
|
|
3581
3599
|
// Gaze down.
|
|
3582
|
-
{ parameter:
|
|
3583
|
-
{ parameter:
|
|
3600
|
+
{ parameter: P7.EyeballY, value: -0.65, blend: "add" },
|
|
3601
|
+
{ parameter: P7.MouthForm, value: 0.4, blend: "add" },
|
|
3584
3602
|
...browsTilt(-0.25)
|
|
3585
3603
|
]
|
|
3586
3604
|
}
|
|
@@ -3600,7 +3618,7 @@ var HEAD_MOTIONS = {
|
|
|
3600
3618
|
duration: 1,
|
|
3601
3619
|
curves: [
|
|
3602
3620
|
{
|
|
3603
|
-
parameter:
|
|
3621
|
+
parameter: P7.AngleY,
|
|
3604
3622
|
keys: [
|
|
3605
3623
|
[0, 0],
|
|
3606
3624
|
[0.3, -18],
|
|
@@ -3621,7 +3639,7 @@ var HEAD_MOTIONS = {
|
|
|
3621
3639
|
fadeIn: 0.15,
|
|
3622
3640
|
curves: [
|
|
3623
3641
|
{
|
|
3624
|
-
parameter:
|
|
3642
|
+
parameter: P7.AngleX,
|
|
3625
3643
|
keys: [
|
|
3626
3644
|
[0, 0],
|
|
3627
3645
|
[0.2, -18],
|
|
@@ -3640,7 +3658,7 @@ var HEAD_MOTIONS = {
|
|
|
3640
3658
|
duration: 1.4,
|
|
3641
3659
|
curves: [
|
|
3642
3660
|
{
|
|
3643
|
-
parameter:
|
|
3661
|
+
parameter: P7.AngleZ,
|
|
3644
3662
|
keys: [
|
|
3645
3663
|
[0, 0],
|
|
3646
3664
|
[0.35, 20],
|
|
@@ -3661,7 +3679,7 @@ var WAVE = {
|
|
|
3661
3679
|
fadeOut: 0.15,
|
|
3662
3680
|
curves: [
|
|
3663
3681
|
{
|
|
3664
|
-
parameter:
|
|
3682
|
+
parameter: P7.ArmPoseRight,
|
|
3665
3683
|
interpolation: "linear",
|
|
3666
3684
|
keys: [
|
|
3667
3685
|
[0, 0],
|
|
@@ -3671,7 +3689,7 @@ var WAVE = {
|
|
|
3671
3689
|
]
|
|
3672
3690
|
},
|
|
3673
3691
|
{
|
|
3674
|
-
parameter:
|
|
3692
|
+
parameter: P7.ArmRight,
|
|
3675
3693
|
keys: [
|
|
3676
3694
|
[0, 0],
|
|
3677
3695
|
[0.4, 10],
|
|
@@ -3680,7 +3698,7 @@ var WAVE = {
|
|
|
3680
3698
|
]
|
|
3681
3699
|
},
|
|
3682
3700
|
{
|
|
3683
|
-
parameter:
|
|
3701
|
+
parameter: P7.ArmPoseAngleRight,
|
|
3684
3702
|
keys: [
|
|
3685
3703
|
[0, 0],
|
|
3686
3704
|
[0.5, 0],
|
|
@@ -3696,7 +3714,7 @@ var WAVE = {
|
|
|
3696
3714
|
]
|
|
3697
3715
|
};
|
|
3698
3716
|
function defaultMotions(declared) {
|
|
3699
|
-
const waves = [
|
|
3717
|
+
const waves = [P7.ArmRight, P7.ArmPoseRight, P7.ArmPoseAngleRight].every(
|
|
3700
3718
|
(id) => declared.has(id)
|
|
3701
3719
|
);
|
|
3702
3720
|
return waves ? { ...HEAD_MOTIONS, ...WAVE } : HEAD_MOTIONS;
|
|
@@ -3932,7 +3950,7 @@ function checkStrandEdges(strand, irises, hair) {
|
|
|
3932
3950
|
}
|
|
3933
3951
|
|
|
3934
3952
|
// src/auto-rig/context.ts
|
|
3935
|
-
import { StandardParameter as
|
|
3953
|
+
import { StandardParameter as P8 } from "@ikijs/format";
|
|
3936
3954
|
|
|
3937
3955
|
// src/auto-rig/head.ts
|
|
3938
3956
|
function outerEdge(runs, axisX, side) {
|
|
@@ -4041,14 +4059,14 @@ function buildHeadFrame(layers, opts) {
|
|
|
4041
4059
|
const rawRows = raw;
|
|
4042
4060
|
const earless = (chin) => {
|
|
4043
4061
|
const rowOf = (y) => Math.round(canvasH / 2 - y - faceLayer.bbox.y - 0.5);
|
|
4044
|
-
const
|
|
4062
|
+
const span2 = Math.max(1, eyeY - chin);
|
|
4045
4063
|
const found = findEars(
|
|
4046
4064
|
rawRows,
|
|
4047
4065
|
wMax,
|
|
4048
4066
|
Math.round(0.2 * rawRows.length),
|
|
4049
4067
|
Math.min(rawRows.length - 1, rowOf(chin)),
|
|
4050
|
-
Math.round(0.1 *
|
|
4051
|
-
Math.round(0.8 *
|
|
4068
|
+
Math.round(0.1 * span2),
|
|
4069
|
+
Math.round(0.8 * span2)
|
|
4052
4070
|
);
|
|
4053
4071
|
if (found === void 0) return profile;
|
|
4054
4072
|
const { top, bottom } = found;
|
|
@@ -4082,16 +4100,16 @@ function buildHeadFrame(layers, opts) {
|
|
|
4082
4100
|
}
|
|
4083
4101
|
}
|
|
4084
4102
|
if (neckRow >= 0 && n - neckRow >= 0.12 * n) {
|
|
4085
|
-
let
|
|
4103
|
+
let span2 = 0;
|
|
4086
4104
|
let waist = Infinity;
|
|
4087
4105
|
for (let r = neckRow; r < n; r++) {
|
|
4088
|
-
|
|
4106
|
+
span2 = Math.max(span2, profile[r]);
|
|
4089
4107
|
if (r < n - tail) waist = Math.min(waist, profile[r]);
|
|
4090
4108
|
}
|
|
4091
4109
|
neck = {
|
|
4092
4110
|
topY: rowY(neckRow),
|
|
4093
4111
|
half: profile[neckRow],
|
|
4094
|
-
span,
|
|
4112
|
+
span: span2,
|
|
4095
4113
|
waist: Math.min(waist, profile[neckRow])
|
|
4096
4114
|
};
|
|
4097
4115
|
}
|
|
@@ -4291,22 +4309,22 @@ function earField(m) {
|
|
|
4291
4309
|
const dy = face(x, y, 0, ay)[1];
|
|
4292
4310
|
const d = Math.abs(x - f.axisX);
|
|
4293
4311
|
const attach = ears.attachAt(y);
|
|
4294
|
-
const
|
|
4312
|
+
const span2 = Math.max(1, ears.outer - attach);
|
|
4295
4313
|
if (sideOf(m, x, s) === "far") {
|
|
4296
4314
|
const rim2 = TURN.earFar * m.face;
|
|
4297
4315
|
const root2 = earRoot(m, face, y, s);
|
|
4298
4316
|
const lead = Math.max(
|
|
4299
4317
|
0,
|
|
4300
|
-
Math.min((1 - m.earFarScale) *
|
|
4318
|
+
Math.min((1 - m.earFarScale) * span2, root2 - rim2)
|
|
4301
4319
|
);
|
|
4302
|
-
let along = rim2 + lead * (ears.outer - d) /
|
|
4320
|
+
let along = rim2 + lead * (ears.outer - d) / span2;
|
|
4303
4321
|
if (d < attach) along = Math.min(along, s * face(x, y, 30 * s, 0)[0]);
|
|
4304
4322
|
return [s * a * along, dy];
|
|
4305
4323
|
}
|
|
4306
4324
|
if (d <= attach) return [face(x, y, ax, 0)[0], dy];
|
|
4307
4325
|
const root = face(f.axisX + Math.sign(x - f.axisX) * attach, y, ax, 0)[0];
|
|
4308
4326
|
const rim = s * a * TURN.earNear * m.face;
|
|
4309
|
-
const t = Math.min(1, (d - attach) /
|
|
4327
|
+
const t = Math.min(1, (d - attach) / span2);
|
|
4310
4328
|
return [root + (rim - root) * t, dy];
|
|
4311
4329
|
};
|
|
4312
4330
|
}
|
|
@@ -4470,7 +4488,17 @@ function hairFrontField(m) {
|
|
|
4470
4488
|
);
|
|
4471
4489
|
return core + (m.hairOuter - core) * out;
|
|
4472
4490
|
};
|
|
4473
|
-
const ride = outerRide(
|
|
4491
|
+
const ride = outerRide(
|
|
4492
|
+
m,
|
|
4493
|
+
inner,
|
|
4494
|
+
coreAt,
|
|
4495
|
+
followAt,
|
|
4496
|
+
eyeY + 1.5 * eyeH,
|
|
4497
|
+
eyeY + eyeH / 2,
|
|
4498
|
+
Math.max(
|
|
4499
|
+
...[m.boxes.iris_L, m.boxes.iris_R].map((b) => b?.y1 ?? -Infinity)
|
|
4500
|
+
)
|
|
4501
|
+
);
|
|
4474
4502
|
const followOf = (x, y, s) => followAt(x, y, s) + (ride === void 0 ? 0 : ride.lift(x, y, s));
|
|
4475
4503
|
const drift = Math.abs(m.hairBack);
|
|
4476
4504
|
const crown = crownRide(m, cap, (x, y, s) => followOf(x, y, s) + drift);
|
|
@@ -4497,9 +4525,12 @@ function crownBlend(m, y) {
|
|
|
4497
4525
|
}
|
|
4498
4526
|
function gridRow(ys, y, snap = false) {
|
|
4499
4527
|
const n = ys.length;
|
|
4500
|
-
|
|
4528
|
+
const yc = clamp(y, ys[n - 1], ys[0]);
|
|
4529
|
+
let r = 0;
|
|
4530
|
+
while (r < n - 2 && ys[r + 1] >= yc) r++;
|
|
4531
|
+
let v = r + (ys[r] - yc) / (ys[r] - ys[r + 1]);
|
|
4501
4532
|
if (snap && Math.abs(v - Math.round(v)) < 1e-3) v = Math.round(v);
|
|
4502
|
-
|
|
4533
|
+
r = Math.min(n - 2, Math.floor(v));
|
|
4503
4534
|
return { r, t: v - r };
|
|
4504
4535
|
}
|
|
4505
4536
|
function gridCol(xs, x) {
|
|
@@ -4589,7 +4620,7 @@ function paints(runs, px) {
|
|
|
4589
4620
|
}
|
|
4590
4621
|
return false;
|
|
4591
4622
|
}
|
|
4592
|
-
function outerRide(m, inner, coreAt, followAt, capLimit) {
|
|
4623
|
+
function outerRide(m, inner, coreAt, followAt, capLimit, eyeTop, irisTop) {
|
|
4593
4624
|
const f = m.frame;
|
|
4594
4625
|
const grid = m.hairFrontGrid;
|
|
4595
4626
|
if (grid === void 0) return void 0;
|
|
@@ -4776,10 +4807,25 @@ function outerRide(m, inner, coreAt, followAt, capLimit) {
|
|
|
4776
4807
|
}
|
|
4777
4808
|
for (let j = 0; j < 2; j++) {
|
|
4778
4809
|
for (let r = n - 2; r >= 0; r--) {
|
|
4779
|
-
if (wr[r] < 1)
|
|
4810
|
+
if (r <= last || wr[r] < 1)
|
|
4780
4811
|
blend[r * 2 + j] = Math.max(blend[r * 2 + j], blend[(r + 1) * 2 + j]);
|
|
4781
4812
|
}
|
|
4782
4813
|
}
|
|
4814
|
+
let zero = last + 1;
|
|
4815
|
+
while (zero < n && ys[zero] > eyeTop) zero++;
|
|
4816
|
+
while (zero > last + 1 && zero < n && ys[zero] < irisTop - 0.5) zero--;
|
|
4817
|
+
const endY = zero < n ? ys[zero] : Math.min(Math.max(eyeTop, irisTop - 0.5), ys[n - 1] - 1);
|
|
4818
|
+
for (let j = 0; j < 2 && last < n - 1; j++) {
|
|
4819
|
+
for (let r = 1; r < zero; r++) {
|
|
4820
|
+
for (let q = 0; q < r && q <= last; q++) {
|
|
4821
|
+
if (wr[q] < 1) continue;
|
|
4822
|
+
blend[r * 2 + j] = Math.max(
|
|
4823
|
+
blend[r * 2 + j],
|
|
4824
|
+
blend[q * 2 + j] * (ys[r] - endY) / (ys[q] - endY)
|
|
4825
|
+
);
|
|
4826
|
+
}
|
|
4827
|
+
}
|
|
4828
|
+
}
|
|
4783
4829
|
const cutRow = (r, j, x) => r > last ? 0 : along(cuts.subarray((r * 2 + j) * nc, (r * 2 + j + 1) * nc), x);
|
|
4784
4830
|
return {
|
|
4785
4831
|
lift: liftAt,
|
|
@@ -5062,7 +5108,7 @@ function renderedLander(turn, parts, grids, from = /* @__PURE__ */ new Map()) {
|
|
|
5062
5108
|
const family = roleSpec(role).family;
|
|
5063
5109
|
const part = parts.find((p) => p.id === role);
|
|
5064
5110
|
const g = grids.get(family);
|
|
5065
|
-
const warp = part?.warps?.find((w) => w.parameter ===
|
|
5111
|
+
const warp = part?.warps?.find((w) => w.parameter === P8.AngleX);
|
|
5066
5112
|
if (part === void 0 || part.mesh === void 0 || !g && !warp) {
|
|
5067
5113
|
return x + field(role)(x, y, ax, 0)[0];
|
|
5068
5114
|
}
|
|
@@ -5201,11 +5247,11 @@ function buildFaceMesh(frame, b, head, neckCells) {
|
|
|
5201
5247
|
const indices = [];
|
|
5202
5248
|
const neck = frame.neck;
|
|
5203
5249
|
if (neck !== void 0) {
|
|
5204
|
-
const
|
|
5205
|
-
const nc = cellsFor(2 *
|
|
5250
|
+
const span2 = neck.span + pad;
|
|
5251
|
+
const nc = cellsFor(2 * span2, neckCells.px, neckCells.min, neckCells.max);
|
|
5206
5252
|
const xs2 = columns(
|
|
5207
|
-
Math.max(b.x0, frame.axisX -
|
|
5208
|
-
Math.min(b.x1, frame.axisX +
|
|
5253
|
+
Math.max(b.x0, frame.axisX - span2),
|
|
5254
|
+
Math.min(b.x1, frame.axisX + span2),
|
|
5209
5255
|
nc
|
|
5210
5256
|
);
|
|
5211
5257
|
const sampleBelow = (x) => Math.max(
|
|
@@ -5341,6 +5387,11 @@ function buildFaceMesh(frame, b, head, neckCells) {
|
|
|
5341
5387
|
return { mesh: { vertices, uvs, indices }, region, headStart };
|
|
5342
5388
|
}
|
|
5343
5389
|
|
|
5390
|
+
// src/auto-rig/patches.ts
|
|
5391
|
+
import {
|
|
5392
|
+
StandardParameter as P9
|
|
5393
|
+
} from "@ikijs/format";
|
|
5394
|
+
|
|
5344
5395
|
// src/auto-rig/solve.ts
|
|
5345
5396
|
var RANGES = {
|
|
5346
5397
|
// [lo, hi, lo exclusive]
|
|
@@ -5771,6 +5822,235 @@ function buildTurn(frame, landmarks, q) {
|
|
|
5771
5822
|
};
|
|
5772
5823
|
}
|
|
5773
5824
|
|
|
5825
|
+
// src/auto-rig/patches.ts
|
|
5826
|
+
var OP_KEYS = ["target", "at", "region", "by", "falloff", "why"];
|
|
5827
|
+
var REGION_KEYS = ["role", "x", "y"];
|
|
5828
|
+
var BY_KEYS = ["dx", "dy"];
|
|
5829
|
+
var ROLLS = /* @__PURE__ */ new Set([P9.AngleZ, P9.BodyAngleZ]);
|
|
5830
|
+
var REST_IS_ART = "rest is the drawn art, which no patch changes (change it with the layout or by regenerating the layer)";
|
|
5831
|
+
function applyPatches(patches, ctx) {
|
|
5832
|
+
if (!Array.isArray(patches)) {
|
|
5833
|
+
throw new PatchError(
|
|
5834
|
+
`patches must be an array, got ${patches === null ? "null" : typeof patches}`
|
|
5835
|
+
);
|
|
5836
|
+
}
|
|
5837
|
+
const sums = /* @__PURE__ */ new Map();
|
|
5838
|
+
const reports = patches.map((op, i) => replay(op, i, ctx, sums));
|
|
5839
|
+
for (const [offsets, s] of sums) {
|
|
5840
|
+
s.terms.forEach((t, k) => {
|
|
5841
|
+
if (t.length === 0) return;
|
|
5842
|
+
const d = t.sort((a, b) => a - b).reduce((a, b) => a + b, 0);
|
|
5843
|
+
offsets[k] = roundTo(offsets[k] + d / s.scale[k % 2], s.step);
|
|
5844
|
+
});
|
|
5845
|
+
}
|
|
5846
|
+
return reports;
|
|
5847
|
+
}
|
|
5848
|
+
function replay(op, i, ctx, sums) {
|
|
5849
|
+
const name = `patches[${i}]`;
|
|
5850
|
+
const o = fields(name, op, OP_KEYS);
|
|
5851
|
+
const target = text(`${name}.target`, o.target);
|
|
5852
|
+
const at = numbers(`${name}.at`, o.at);
|
|
5853
|
+
const region = fields(`${name}.region`, o.region, REGION_KEYS);
|
|
5854
|
+
const role = text(`${name}.region.role`, region.role);
|
|
5855
|
+
const x = span(`${name}.region.x`, region.x);
|
|
5856
|
+
const y = span(`${name}.region.y`, region.y);
|
|
5857
|
+
const by = fields(`${name}.by`, o.by, BY_KEYS);
|
|
5858
|
+
const dx = finite2(`${name}.by.dx`, by.dx);
|
|
5859
|
+
const dy = finite2(`${name}.by.dy`, by.dy);
|
|
5860
|
+
const falloff = o.falloff === void 0 ? 0 : finite2(`${name}.falloff`, o.falloff, 0);
|
|
5861
|
+
if (o.why !== void 0) text(`${name}.why`, o.why);
|
|
5862
|
+
const box = ctx.boxOf(role);
|
|
5863
|
+
if (box === void 0) {
|
|
5864
|
+
const roles = [...new Set(ctx.parts.map((p) => roleOfPart(p.id)))].filter(
|
|
5865
|
+
(r) => ctx.boxOf(r) !== void 0
|
|
5866
|
+
);
|
|
5867
|
+
throw new PatchError(
|
|
5868
|
+
`${name}.region.role "${role}" is not a layer of this set (roles: ${roles.join(", ")})`
|
|
5869
|
+
);
|
|
5870
|
+
}
|
|
5871
|
+
const stop = resolveStop(name, target, at, ctx);
|
|
5872
|
+
const { points } = stop;
|
|
5873
|
+
const weights = regionWeights(points, box, x, y, falloff * ctx.faceWidth);
|
|
5874
|
+
const hit = weights.flatMap((w, k) => w > 0 ? [k] : []);
|
|
5875
|
+
if (hit.length === 0) {
|
|
5876
|
+
const fx = points.map(([px]) => (px - box.x0) / bw(box));
|
|
5877
|
+
const fy = points.map(([, py]) => (box.y1 - py) / bh(box));
|
|
5878
|
+
const range = (v) => `[${roundTo(Math.min(...v), 0.01)}, ${roundTo(Math.max(...v), 0.01)}]`;
|
|
5879
|
+
throw new PatchError(
|
|
5880
|
+
`${name}.region (falloff ${falloff}) catches none of ${target}'s ${points.length} points; at rest they span x ${range(fx)}, y ${range(fy)} of ${role}'s box`
|
|
5881
|
+
);
|
|
5882
|
+
}
|
|
5883
|
+
let sum = sums.get(stop.offsets);
|
|
5884
|
+
if (sum === void 0) {
|
|
5885
|
+
sum = {
|
|
5886
|
+
terms: stop.offsets.map(() => []),
|
|
5887
|
+
scale: stop.scale,
|
|
5888
|
+
step: stop.step
|
|
5889
|
+
};
|
|
5890
|
+
sums.set(stop.offsets, sum);
|
|
5891
|
+
}
|
|
5892
|
+
const ddx = dx * ctx.faceWidth;
|
|
5893
|
+
const ddy = -dy * ctx.faceWidth;
|
|
5894
|
+
for (const k of hit) {
|
|
5895
|
+
sum.terms[2 * k].push(ddx * weights[k]);
|
|
5896
|
+
sum.terms[2 * k + 1].push(ddy * weights[k]);
|
|
5897
|
+
}
|
|
5898
|
+
const maxW = Math.max(...hit.map((k) => weights[k]));
|
|
5899
|
+
const report = {
|
|
5900
|
+
index: i,
|
|
5901
|
+
target,
|
|
5902
|
+
at,
|
|
5903
|
+
moved: hit.length,
|
|
5904
|
+
of: points.length,
|
|
5905
|
+
maxPx: roundTo(maxW * Math.hypot(dx, dy) * ctx.faceWidth, 0.1)
|
|
5906
|
+
};
|
|
5907
|
+
if (stop.stride !== void 0) {
|
|
5908
|
+
const { stride } = stop;
|
|
5909
|
+
const cols = hit.map((k) => k % stride);
|
|
5910
|
+
const rows = hit.map((k) => Math.floor(k / stride));
|
|
5911
|
+
report.cols = [Math.min(...cols), Math.max(...cols)];
|
|
5912
|
+
report.rows = [Math.min(...rows), Math.max(...rows)];
|
|
5913
|
+
}
|
|
5914
|
+
return report;
|
|
5915
|
+
}
|
|
5916
|
+
function resolveStop(name, target, at, ctx) {
|
|
5917
|
+
const grid = ctx.grids.get(target);
|
|
5918
|
+
if (grid !== void 0) {
|
|
5919
|
+
const l = grid.lattice.points;
|
|
5920
|
+
const points = [];
|
|
5921
|
+
for (let k = 0; k < l.length; k += 2) points.push([l[k], l[k + 1]]);
|
|
5922
|
+
return {
|
|
5923
|
+
offsets: gridStop(name, at, target, grid.keyforms),
|
|
5924
|
+
points,
|
|
5925
|
+
scale: [1, 1],
|
|
5926
|
+
step: 0.1,
|
|
5927
|
+
stride: grid.lattice.cols + 1
|
|
5928
|
+
};
|
|
5929
|
+
}
|
|
5930
|
+
const part = ctx.parts.find((p) => p.id === target);
|
|
5931
|
+
const b = ctx.boxOf(target);
|
|
5932
|
+
if (part?.mesh === void 0 || part.warps === void 0 || b === void 0) {
|
|
5933
|
+
const parts = ctx.parts.filter(
|
|
5934
|
+
(p) => p.mesh !== void 0 && p.warps !== void 0 && ctx.boxOf(p.id) !== void 0
|
|
5935
|
+
);
|
|
5936
|
+
throw new PatchError(
|
|
5937
|
+
`${name}.target "${target}" is neither a feature grid (${[...ctx.grids.keys()].join(", ")}) nor a mesh part with warps (${parts.map((p) => p.id).join(", ")}); bodyWarp and the matrix deformers are not patchable`
|
|
5938
|
+
);
|
|
5939
|
+
}
|
|
5940
|
+
return {
|
|
5941
|
+
offsets: partStop(name, at, part, part.warps, ctx),
|
|
5942
|
+
points: meshPoints(part.mesh, b),
|
|
5943
|
+
scale: [bw(b), bh(b)],
|
|
5944
|
+
step: 1e-4
|
|
5945
|
+
};
|
|
5946
|
+
}
|
|
5947
|
+
function gridStop(name, at, id, keyforms) {
|
|
5948
|
+
const i = X_STOPS.indexOf(at[P9.AngleX]);
|
|
5949
|
+
const j = Y_STOPS.indexOf(at[P9.AngleY]);
|
|
5950
|
+
if (Object.keys(at).length !== 2 || i < 0 || j < 0) {
|
|
5951
|
+
throw new PatchError(
|
|
5952
|
+
`${name}.at must name ${P9.AngleX} in [${X_STOPS.join(", ")}] and ${P9.AngleY} in [${Y_STOPS.join(", ")}] for the grid ${id}, got ${JSON.stringify(at)}`
|
|
5953
|
+
);
|
|
5954
|
+
}
|
|
5955
|
+
if (X_STOPS[i] === 0 && Y_STOPS[j] === 0) {
|
|
5956
|
+
throw new PatchError(
|
|
5957
|
+
`${name}.at ${P9.AngleX}=0 ${P9.AngleY}=0 is ${id}'s rest stop: ${REST_IS_ART}; patch a stop with ${P9.AngleX} or ${P9.AngleY} at \xB130 instead`
|
|
5958
|
+
);
|
|
5959
|
+
}
|
|
5960
|
+
return keyforms[j * X_STOPS.length + i];
|
|
5961
|
+
}
|
|
5962
|
+
function partStop(name, at, part, warps, ctx) {
|
|
5963
|
+
const keys = Object.keys(at);
|
|
5964
|
+
const [param] = keys;
|
|
5965
|
+
const value = at[param];
|
|
5966
|
+
const keyform = keys.length === 1 ? warps.filter((w) => w.parameter === param).flatMap((w) => w.keyforms).find((k) => k.value === value) : void 0;
|
|
5967
|
+
if (keyform === void 0) {
|
|
5968
|
+
throw new PatchError(
|
|
5969
|
+
`${name}.at must name one parameter at one of ${part.id}'s warp stops (${listStops(warps, () => true)}), got ${JSON.stringify(at)}`
|
|
5970
|
+
);
|
|
5971
|
+
}
|
|
5972
|
+
if (ROLLS.has(param)) {
|
|
5973
|
+
throw new PatchError(
|
|
5974
|
+
`${name}.at ${param}: that parameter rolls ${part.id}'s deformer chain, so a displacement at its stop is not on screen axes; patch another of its stops (${listStops(warps, (p) => !ROLLS.has(p))}) or its feature grid instead`
|
|
5975
|
+
);
|
|
5976
|
+
}
|
|
5977
|
+
const isRest = (p, v) => v === ctx.parameters.find((q) => q.id === p)?.default;
|
|
5978
|
+
const isShut = (p, v) => SHUT_BY.get(part.id) === p && v === 0;
|
|
5979
|
+
if (!isRest(param, value) && !isShut(param, value)) return keyform.offsets;
|
|
5980
|
+
const open = listStops(warps, (p, v) => !isRest(p, v) && !isShut(p, v));
|
|
5981
|
+
const grid = part.deformer !== void 0 && ctx.grids.has(part.deformer) ? `its feature grid ${part.deformer} at a ${P9.AngleX} \xD7 ${P9.AngleY} stop` : "";
|
|
5982
|
+
const instead = [open && `another of its stops (${open})`, grid].filter((s) => s !== "").join(" or ");
|
|
5983
|
+
const hint = instead === "" ? "" : `; patch ${instead} instead`;
|
|
5984
|
+
throw new PatchError(
|
|
5985
|
+
isRest(param, value) ? `${name}.at ${param}=${value} is that parameter's default, ${part.id}'s rest stop: ${REST_IS_ART}${hint}` : `${name}.at ${param}=0 is where the rig shuts ${part.id} on purpose, a closure the fold check exempts, so a fold a patch made there would never be reported${hint}`
|
|
5986
|
+
);
|
|
5987
|
+
}
|
|
5988
|
+
function listStops(warps, keep) {
|
|
5989
|
+
const stops = /* @__PURE__ */ new Map();
|
|
5990
|
+
for (const w of warps) {
|
|
5991
|
+
for (const k of w.keyforms) {
|
|
5992
|
+
if (!keep(w.parameter, k.value)) continue;
|
|
5993
|
+
const s = stops.get(w.parameter) ?? /* @__PURE__ */ new Set();
|
|
5994
|
+
s.add(k.value);
|
|
5995
|
+
stops.set(w.parameter, s);
|
|
5996
|
+
}
|
|
5997
|
+
}
|
|
5998
|
+
return [...stops].map(([p, s]) => `${p}@${[...s].sort((a, b) => a - b).join(",")}`).join(" ");
|
|
5999
|
+
}
|
|
6000
|
+
function regionWeights(points, box, x, y, fall) {
|
|
6001
|
+
const x0 = box.x0 + x[0] * bw(box);
|
|
6002
|
+
const x1 = box.x0 + x[1] * bw(box);
|
|
6003
|
+
const top = box.y1 - y[0] * bh(box);
|
|
6004
|
+
const bottom = box.y1 - y[1] * bh(box);
|
|
6005
|
+
return points.map(([px, py]) => {
|
|
6006
|
+
const d = Math.hypot(
|
|
6007
|
+
Math.max(x0 - px, 0, px - x1),
|
|
6008
|
+
Math.max(bottom - py, 0, py - top)
|
|
6009
|
+
);
|
|
6010
|
+
return d === 0 ? 1 : d < fall ? smoothstep(fall, 0, d) : 0;
|
|
6011
|
+
});
|
|
6012
|
+
}
|
|
6013
|
+
function asPatch(check) {
|
|
6014
|
+
try {
|
|
6015
|
+
return check();
|
|
6016
|
+
} catch (e) {
|
|
6017
|
+
throw e instanceof TurnTargetError ? new PatchError(e.message) : e;
|
|
6018
|
+
}
|
|
6019
|
+
}
|
|
6020
|
+
function fields(name, v, known) {
|
|
6021
|
+
if (v === void 0) throw new PatchError(`${name} is missing`);
|
|
6022
|
+
asPatch(() => checkedKeys(name, v, known));
|
|
6023
|
+
return v;
|
|
6024
|
+
}
|
|
6025
|
+
function numbers(name, v) {
|
|
6026
|
+
const o = fields(name, v, Object.keys(v ?? {}));
|
|
6027
|
+
return Object.fromEntries(
|
|
6028
|
+
Object.entries(o).map(([k, n]) => [k, finite2(`${name}.${k}`, n)])
|
|
6029
|
+
);
|
|
6030
|
+
}
|
|
6031
|
+
var finite2 = (name, v, lo = -Infinity) => asPatch(() => checkedNumber(name, v, lo, Infinity));
|
|
6032
|
+
function text(name, v) {
|
|
6033
|
+
if (typeof v !== "string") {
|
|
6034
|
+
throw new PatchError(`${name} must be a string, got ${String(v)}`);
|
|
6035
|
+
}
|
|
6036
|
+
return v;
|
|
6037
|
+
}
|
|
6038
|
+
function span(name, v) {
|
|
6039
|
+
if (!Array.isArray(v) || v.length !== 2) {
|
|
6040
|
+
throw new PatchError(
|
|
6041
|
+
`${name} must be [from, to], got ${JSON.stringify(v)}`
|
|
6042
|
+
);
|
|
6043
|
+
}
|
|
6044
|
+
const lo = finite2(`${name}[0]`, v[0]);
|
|
6045
|
+
const hi = finite2(`${name}[1]`, v[1]);
|
|
6046
|
+
if (lo > hi) {
|
|
6047
|
+
throw new PatchError(
|
|
6048
|
+
`${name} [${lo}, ${hi}] runs backwards: ${name}[0] must not exceed ${name}[1]`
|
|
6049
|
+
);
|
|
6050
|
+
}
|
|
6051
|
+
return [lo, hi];
|
|
6052
|
+
}
|
|
6053
|
+
|
|
5774
6054
|
// src/auto-rig/generate.ts
|
|
5775
6055
|
var MESH_CELLS = {
|
|
5776
6056
|
face: { px: 28, min: 4, max: 24 },
|
|
@@ -5828,7 +6108,7 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
5828
6108
|
const solveFrame = buildHeadFrame(layers, frameOptions);
|
|
5829
6109
|
const ctx = solveContext(solveFrame, byRole, turnOptions, hasNose);
|
|
5830
6110
|
const boxes = Object.fromEntries(
|
|
5831
|
-
["eye_L", "eye_R", "brow_L", "brow_R"].filter(has).map((r) => [r, box(r)])
|
|
6111
|
+
["eye_L", "eye_R", "iris_L", "iris_R", "brow_L", "brow_R"].filter(has).map((r) => [r, box(r)])
|
|
5832
6112
|
);
|
|
5833
6113
|
const landmarks = {
|
|
5834
6114
|
boxes,
|
|
@@ -5948,6 +6228,15 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
5948
6228
|
];
|
|
5949
6229
|
}
|
|
5950
6230
|
const parameters = declareParameters(new Set(byRole.keys()));
|
|
6231
|
+
const reports = options.patches === void 0 ? void 0 : applyPatches(options.patches, {
|
|
6232
|
+
grids: new Map(
|
|
6233
|
+
[...grids].map(([family, g]) => [WARP_ID[family], g])
|
|
6234
|
+
),
|
|
6235
|
+
parts,
|
|
6236
|
+
boxOf: (role) => has(role) ? box(role) : void 0,
|
|
6237
|
+
faceWidth: byRole.get("face").cropW,
|
|
6238
|
+
parameters
|
|
6239
|
+
});
|
|
5951
6240
|
const model = {
|
|
5952
6241
|
version: IKI_FORMAT_VERSION2,
|
|
5953
6242
|
name: "auto-rigged",
|
|
@@ -5960,6 +6249,7 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
5960
6249
|
motions: defaultMotions(new Set(parameters.map((p) => p.id)))
|
|
5961
6250
|
};
|
|
5962
6251
|
const valid = parseIkiModel3(model);
|
|
6252
|
+
if (reports !== void 0) options.onPatchesApplied?.(reports);
|
|
5963
6253
|
if (hasNose && options.onTurnSolved) {
|
|
5964
6254
|
options.onTurnSolved(
|
|
5965
6255
|
turnReport(
|
|
@@ -5974,14 +6264,14 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
5974
6264
|
function turnWarps(mesh, b, fieldOf) {
|
|
5975
6265
|
return [
|
|
5976
6266
|
localWarp(
|
|
5977
|
-
|
|
6267
|
+
P10.AngleX,
|
|
5978
6268
|
mesh,
|
|
5979
6269
|
b,
|
|
5980
6270
|
X_STOPS,
|
|
5981
6271
|
([x, y], v, i) => v === 0 ? [0, 0] : [fieldOf(i)(x, y, v, 0)[0], 0]
|
|
5982
6272
|
),
|
|
5983
6273
|
localWarp(
|
|
5984
|
-
|
|
6274
|
+
P10.AngleY,
|
|
5985
6275
|
mesh,
|
|
5986
6276
|
b,
|
|
5987
6277
|
Y_STOPS,
|
|
@@ -6032,7 +6322,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
|
|
|
6032
6322
|
}
|
|
6033
6323
|
if (mesh !== void 0) part.mesh = mesh;
|
|
6034
6324
|
const side = spec.role.endsWith("_L") ? "L" : "R";
|
|
6035
|
-
const eyeOpen = side === "L" ?
|
|
6325
|
+
const eyeOpen = side === "L" ? P10.EyeOpenLeft : P10.EyeOpenRight;
|
|
6036
6326
|
const hh = frame.hh;
|
|
6037
6327
|
switch (spec.role) {
|
|
6038
6328
|
case "face":
|
|
@@ -6047,7 +6337,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
|
|
|
6047
6337
|
headOwnRoll(byRole.has("body"))
|
|
6048
6338
|
),
|
|
6049
6339
|
localWarp(
|
|
6050
|
-
|
|
6340
|
+
P10.Breath,
|
|
6051
6341
|
mesh,
|
|
6052
6342
|
b,
|
|
6053
6343
|
[0, 1],
|
|
@@ -6094,8 +6384,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
|
|
|
6094
6384
|
if (byRole.has("mouth_open")) {
|
|
6095
6385
|
warps.push(mouthWiden(mesh, b));
|
|
6096
6386
|
bindings.push(
|
|
6097
|
-
{ parameter:
|
|
6098
|
-
{ parameter:
|
|
6387
|
+
{ parameter: P10.MouthOpen, channel: "opacity", from: 1, to: 0 },
|
|
6388
|
+
{ parameter: P10.MouthOpen, channel: "opacity", from: 1, to: 0 }
|
|
6099
6389
|
);
|
|
6100
6390
|
extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
|
|
6101
6391
|
} else {
|
|
@@ -6105,19 +6395,19 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
|
|
|
6105
6395
|
);
|
|
6106
6396
|
bindings.push(
|
|
6107
6397
|
{
|
|
6108
|
-
parameter:
|
|
6398
|
+
parameter: P10.MouthOpen,
|
|
6109
6399
|
channel: "scaleY",
|
|
6110
6400
|
from: 0,
|
|
6111
6401
|
to: roundTo(grow, 0.01)
|
|
6112
6402
|
},
|
|
6113
6403
|
{
|
|
6114
|
-
parameter:
|
|
6404
|
+
parameter: P10.MouthOpen,
|
|
6115
6405
|
channel: "scaleX",
|
|
6116
6406
|
from: 0,
|
|
6117
6407
|
to: roundTo(AMPLITUDE.mouthOpenWidth - 1, 0.01)
|
|
6118
6408
|
},
|
|
6119
6409
|
{
|
|
6120
|
-
parameter:
|
|
6410
|
+
parameter: P10.MouthOpen,
|
|
6121
6411
|
channel: "translateY",
|
|
6122
6412
|
from: 0,
|
|
6123
6413
|
to: -roundTo(grow * bh(b) / 2, 0.01)
|
|
@@ -6129,7 +6419,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
|
|
|
6129
6419
|
case "mouth_open":
|
|
6130
6420
|
warps.push(mouthForm(mesh, b), mouthOpenGrow(mesh, b));
|
|
6131
6421
|
bindings.push({
|
|
6132
|
-
parameter:
|
|
6422
|
+
parameter: P10.MouthOpen,
|
|
6133
6423
|
channel: "opacity",
|
|
6134
6424
|
from: 0,
|
|
6135
6425
|
to: 1
|
|
@@ -6151,7 +6441,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
|
|
|
6151
6441
|
case "blush_L":
|
|
6152
6442
|
case "blush_R":
|
|
6153
6443
|
bindings.push({
|
|
6154
|
-
parameter:
|
|
6444
|
+
parameter: P10.Cheek,
|
|
6155
6445
|
channel: "opacity",
|
|
6156
6446
|
from: AMPLITUDE.blushRest,
|
|
6157
6447
|
to: 1
|
|
@@ -6162,8 +6452,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
|
|
|
6162
6452
|
if (byRole.has("hair_front")) {
|
|
6163
6453
|
const amp = style.sway * AMPLITUDE.sway * hh;
|
|
6164
6454
|
warps.push(
|
|
6165
|
-
hairSway(
|
|
6166
|
-
hairSway(
|
|
6455
|
+
hairSway(P10.HairSwayX, mesh, b, amp),
|
|
6456
|
+
hairSway(P10.HairSwayZ, mesh, b, amp)
|
|
6167
6457
|
);
|
|
6168
6458
|
}
|
|
6169
6459
|
warps.push(tiltHang(mesh, b, pivot, hairHangWeight(frame.chinY, b.y0)));
|
|
@@ -6178,6 +6468,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
|
|
|
6178
6468
|
headStart
|
|
6179
6469
|
};
|
|
6180
6470
|
}
|
|
6471
|
+
var RAMP_ROWS = 3;
|
|
6472
|
+
var MIN_RAMP_CELL = 4;
|
|
6181
6473
|
function hairFrontGrid(b, frame, boxes) {
|
|
6182
6474
|
const cells = MESH_CELLS.hair_front;
|
|
6183
6475
|
const px = Math.min(cells.px, meshScale("hair_front", frame));
|
|
@@ -6202,6 +6494,24 @@ function hairFrontGrid(b, frame, boxes) {
|
|
|
6202
6494
|
{ length: rows + 1 },
|
|
6203
6495
|
(_, r) => b.y1 - bh(b) * r / rows
|
|
6204
6496
|
);
|
|
6497
|
+
const eyes = [boxes.eye_L, boxes.eye_R].filter((e) => e !== void 0);
|
|
6498
|
+
const irises = [boxes.iris_L, boxes.iris_R].filter((e) => e !== void 0);
|
|
6499
|
+
if (eyes.length > 0 && irises.length > 0) {
|
|
6500
|
+
const eyeY = eyes.reduce((t, e) => t + (e.y0 + e.y1) / 2, 0) / eyes.length;
|
|
6501
|
+
const eyeH = eyes.reduce((t, e) => t + (e.y1 - e.y0), 0) / eyes.length;
|
|
6502
|
+
const hi = Math.min(eyeY + 1.5 * eyeH, b.y1);
|
|
6503
|
+
const lo = Math.max(Math.max(...irises.map((e) => e.y1)) - 0.5, b.y0);
|
|
6504
|
+
for (; ; ) {
|
|
6505
|
+
const edges = [hi, ...ys.filter((y) => y < hi && y > lo), lo];
|
|
6506
|
+
if (edges.length - 2 >= RAMP_ROWS) break;
|
|
6507
|
+
let gap = 0;
|
|
6508
|
+
for (let i = 1; i + 1 < edges.length; i++)
|
|
6509
|
+
if (edges[i] - edges[i + 1] > edges[gap] - edges[gap + 1]) gap = i;
|
|
6510
|
+
if (edges[gap] - edges[gap + 1] < 2 * MIN_RAMP_CELL) break;
|
|
6511
|
+
ys.push((edges[gap] + edges[gap + 1]) / 2);
|
|
6512
|
+
ys.sort((p, q) => q - p);
|
|
6513
|
+
}
|
|
6514
|
+
}
|
|
6205
6515
|
return { xs, ys };
|
|
6206
6516
|
}
|
|
6207
6517
|
function meshScale(role, frame) {
|
|
@@ -6229,9 +6539,9 @@ function deformers(frame, grids, bodyWarp, arms, byRole) {
|
|
|
6229
6539
|
pivot: { x: roundTo(frame.axisX, 0.01), y: roundTo(frame.chinY, 0.01) },
|
|
6230
6540
|
bindings: [
|
|
6231
6541
|
// AngleZ is clockwise-positive (Live2D's); a rotation is CCW-positive.
|
|
6232
|
-
{ parameter:
|
|
6542
|
+
{ parameter: P10.AngleZ, channel: "rotate", from: roll, to: -roll },
|
|
6233
6543
|
{
|
|
6234
|
-
parameter:
|
|
6544
|
+
parameter: P10.Breath,
|
|
6235
6545
|
channel: "translateY",
|
|
6236
6546
|
from: 0,
|
|
6237
6547
|
to: roundTo(headOwnBreath(hasBody) * frame.hh, 0.01)
|
|
@@ -6249,8 +6559,8 @@ function deformers(frame, grids, bodyWarp, arms, byRole) {
|
|
|
6249
6559
|
points: g.lattice.points
|
|
6250
6560
|
},
|
|
6251
6561
|
warp2d: {
|
|
6252
|
-
parameter:
|
|
6253
|
-
parameterY:
|
|
6562
|
+
parameter: P10.AngleX,
|
|
6563
|
+
parameterY: P10.AngleY,
|
|
6254
6564
|
valuesX: X_STOPS,
|
|
6255
6565
|
valuesY: Y_STOPS,
|
|
6256
6566
|
keyforms2d: g.keyforms.map((offsets) => ({ offsets }))
|
|
@@ -6263,16 +6573,16 @@ function hairPhysics() {
|
|
|
6263
6573
|
return [
|
|
6264
6574
|
{
|
|
6265
6575
|
id: "hairSway",
|
|
6266
|
-
input: { parameter:
|
|
6267
|
-
output: { parameter:
|
|
6576
|
+
input: { parameter: P10.AngleX, weight: 1 },
|
|
6577
|
+
output: { parameter: P10.HairSwayX, scale: 5 },
|
|
6268
6578
|
mass: 1,
|
|
6269
6579
|
stiffness: 30,
|
|
6270
6580
|
damping: 3
|
|
6271
6581
|
},
|
|
6272
6582
|
{
|
|
6273
6583
|
id: "hairTilt",
|
|
6274
|
-
input: { parameter:
|
|
6275
|
-
output: { parameter:
|
|
6584
|
+
input: { parameter: P10.AngleZ, weight: 1 },
|
|
6585
|
+
output: { parameter: P10.HairSwayZ, scale: 8 },
|
|
6276
6586
|
mass: 1,
|
|
6277
6587
|
stiffness: 25,
|
|
6278
6588
|
damping: 2.5
|
|
@@ -6282,13 +6592,13 @@ function hairPhysics() {
|
|
|
6282
6592
|
function declareParameters(roles) {
|
|
6283
6593
|
const out = [];
|
|
6284
6594
|
const add = (id, name, min, max, def) => out.push({ id, name, min, max, default: def });
|
|
6285
|
-
add(
|
|
6286
|
-
add(
|
|
6287
|
-
add(
|
|
6595
|
+
add(P10.AngleX, "Head Angle", -30, 30, 0);
|
|
6596
|
+
add(P10.AngleY, "Head Angle Y", -30, 30, 0);
|
|
6597
|
+
add(P10.AngleZ, "Head Angle Z", -30, 30, 0);
|
|
6288
6598
|
if (roles.has("body")) {
|
|
6289
|
-
add(
|
|
6290
|
-
add(
|
|
6291
|
-
add(
|
|
6599
|
+
add(P10.BodyAngleX, "Body Angle X", -10, 10, 0);
|
|
6600
|
+
add(P10.BodyAngleY, "Body Angle Y", -10, 10, 0);
|
|
6601
|
+
add(P10.BodyAngleZ, "Body Angle Z", -10, 10, 0);
|
|
6292
6602
|
}
|
|
6293
6603
|
const addPose = (side, pose, angle) => {
|
|
6294
6604
|
add(pose, `Arm Pose ${side}`, 0, 1, 0);
|
|
@@ -6297,40 +6607,40 @@ function declareParameters(roles) {
|
|
|
6297
6607
|
const [armMin, armMax] = ARM_RANGE;
|
|
6298
6608
|
const [elbowMin, elbowMax] = ELBOW_RANGE;
|
|
6299
6609
|
if (roles.has("arm_L")) {
|
|
6300
|
-
add(
|
|
6301
|
-
add(
|
|
6610
|
+
add(P10.ArmLeft, "Arm L", armMin, armMax, 0);
|
|
6611
|
+
add(P10.ElbowLeft, "Elbow L", elbowMin, elbowMax, 0);
|
|
6302
6612
|
if (roles.has("forearm_pose_L"))
|
|
6303
|
-
addPose("L",
|
|
6613
|
+
addPose("L", P10.ArmPoseLeft, P10.ArmPoseAngleLeft);
|
|
6304
6614
|
}
|
|
6305
6615
|
if (roles.has("arm_R")) {
|
|
6306
|
-
add(
|
|
6307
|
-
add(
|
|
6616
|
+
add(P10.ArmRight, "Arm R", armMin, armMax, 0);
|
|
6617
|
+
add(P10.ElbowRight, "Elbow R", elbowMin, elbowMax, 0);
|
|
6308
6618
|
if (roles.has("forearm_pose_R"))
|
|
6309
|
-
addPose("R",
|
|
6619
|
+
addPose("R", P10.ArmPoseRight, P10.ArmPoseAngleRight);
|
|
6310
6620
|
}
|
|
6311
|
-
add(
|
|
6312
|
-
add(
|
|
6621
|
+
add(P10.EyeOpenLeft, "Eye L", 0, 1, 1);
|
|
6622
|
+
add(P10.EyeOpenRight, "Eye R", 0, 1, 1);
|
|
6313
6623
|
if ([...roles].some((r) => /^(iris|pupil|highlight)_/.test(r))) {
|
|
6314
|
-
add(
|
|
6315
|
-
add(
|
|
6624
|
+
add(P10.EyeballX, "Gaze X", -1, 1, 0);
|
|
6625
|
+
add(P10.EyeballY, "Gaze Y", -1, 1, 0);
|
|
6316
6626
|
}
|
|
6317
|
-
add(
|
|
6318
|
-
add(
|
|
6627
|
+
add(P10.MouthOpen, "Mouth Open", 0, 1, 0);
|
|
6628
|
+
add(P10.MouthForm, "Mouth Form", -1, 1, 0);
|
|
6319
6629
|
if (roles.has("brow_L")) {
|
|
6320
|
-
add(
|
|
6321
|
-
add(
|
|
6630
|
+
add(P10.BrowLeftY, "Brow L Y", -1, 1, 0);
|
|
6631
|
+
add(P10.BrowLeftAngle, "Brow L Angle", -1, 1, 0);
|
|
6322
6632
|
}
|
|
6323
6633
|
if (roles.has("brow_R")) {
|
|
6324
|
-
add(
|
|
6325
|
-
add(
|
|
6634
|
+
add(P10.BrowRightY, "Brow R Y", -1, 1, 0);
|
|
6635
|
+
add(P10.BrowRightAngle, "Brow R Angle", -1, 1, 0);
|
|
6326
6636
|
}
|
|
6327
6637
|
if (roles.has("blush_L") || roles.has("blush_R")) {
|
|
6328
|
-
add(
|
|
6638
|
+
add(P10.Cheek, "Cheek", 0, 1, 0);
|
|
6329
6639
|
}
|
|
6330
|
-
add(
|
|
6640
|
+
add(P10.Breath, "Breath", 0, 1, 0);
|
|
6331
6641
|
if (roles.has("hair_front")) {
|
|
6332
|
-
add(
|
|
6333
|
-
add(
|
|
6642
|
+
add(P10.HairSwayX, "Hair Sway X", -SWAY_RANGE, SWAY_RANGE, 0);
|
|
6643
|
+
add(P10.HairSwayZ, "Hair Sway Z", -SWAY_RANGE, SWAY_RANGE, 0);
|
|
6334
6644
|
}
|
|
6335
6645
|
return out;
|
|
6336
6646
|
}
|
|
@@ -6351,6 +6661,8 @@ export {
|
|
|
6351
6661
|
LIP_INSIDE_ROLES,
|
|
6352
6662
|
LIP_ROLES,
|
|
6353
6663
|
LayerGeometryError,
|
|
6664
|
+
PatchError,
|
|
6665
|
+
SHUT_BY,
|
|
6354
6666
|
SPECK_CORE_FRACTION,
|
|
6355
6667
|
SetDeformerBindings,
|
|
6356
6668
|
SetDeformerParent,
|