@ikijs/editor 0.20.0 → 0.21.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 +1 -1
- package/dist/index.d.mts +18 -10
- package/dist/index.d.ts +18 -10
- package/dist/index.js +321 -90
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +322 -89
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1644,7 +1644,10 @@ var ROW_RUN_ROLES = /* @__PURE__ */ new Set([
|
|
|
1644
1644
|
"arm_L",
|
|
1645
1645
|
"arm_R",
|
|
1646
1646
|
"forearm_pose_L",
|
|
1647
|
-
"forearm_pose_R"
|
|
1647
|
+
"forearm_pose_R",
|
|
1648
|
+
"mouth_inner",
|
|
1649
|
+
"lip_lower",
|
|
1650
|
+
"lip_upper"
|
|
1648
1651
|
]);
|
|
1649
1652
|
var REST_HIDDEN_ROLES = /* @__PURE__ */ new Set([
|
|
1650
1653
|
"forearm_pose_L",
|
|
@@ -2257,6 +2260,29 @@ function gridMesh(cols, rows) {
|
|
|
2257
2260
|
}
|
|
2258
2261
|
return { vertices, uvs, indices };
|
|
2259
2262
|
}
|
|
2263
|
+
function columnMesh(b, grid) {
|
|
2264
|
+
const { xs } = grid;
|
|
2265
|
+
const rows = grid.ys.length - 1;
|
|
2266
|
+
const vertices = [];
|
|
2267
|
+
const uvs = [];
|
|
2268
|
+
for (let r = 0; r <= rows; r++) {
|
|
2269
|
+
for (const x of xs) {
|
|
2270
|
+
const u = (x - b.x0) / bw(b);
|
|
2271
|
+
vertices.push(roundTo(u - 0.5, 1e-5), roundTo(0.5 - r / rows, 1e-5));
|
|
2272
|
+
uvs.push(roundTo(u, 1e-5), roundTo(r / rows, 1e-5));
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
const n = xs.length;
|
|
2276
|
+
const indices = [];
|
|
2277
|
+
for (let r = 0; r < rows; r++) {
|
|
2278
|
+
for (let c = 0; c + 1 < n; c++) {
|
|
2279
|
+
const tl = r * n + c;
|
|
2280
|
+
const bl = tl + n;
|
|
2281
|
+
indices.push(bl, bl + 1, tl, tl, bl + 1, tl + 1);
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
return { vertices, uvs, indices };
|
|
2285
|
+
}
|
|
2260
2286
|
function meshPoints(mesh, box) {
|
|
2261
2287
|
const out = [];
|
|
2262
2288
|
for (let i = 0; i < mesh.vertices.length; i += 2) {
|
|
@@ -2380,10 +2406,13 @@ function browBindings(side, reach) {
|
|
|
2380
2406
|
];
|
|
2381
2407
|
}
|
|
2382
2408
|
var BROW_TILT = 15;
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2409
|
+
var mouthFrameOf = (box) => ({
|
|
2410
|
+
c: cx(box),
|
|
2411
|
+
half: bw(box) / 2,
|
|
2412
|
+
amp: 0.3 * bh(box)
|
|
2413
|
+
});
|
|
2414
|
+
function mouthForm(mesh, box, frame = mouthFrameOf(box)) {
|
|
2415
|
+
const { c, half, amp: a } = frame;
|
|
2387
2416
|
return localWarp(import_format3.StandardParameter.MouthForm, mesh, box, [-1, 0, 1], ([x], v) => {
|
|
2388
2417
|
if (v === 0) return [0, 0];
|
|
2389
2418
|
const u = (x - c) / half;
|
|
@@ -2401,8 +2430,8 @@ function mouthOpenGrow(mesh, box) {
|
|
|
2401
2430
|
([x, y], v) => v === 1 ? [(AMPLITUDE.mouthOpenWidth - 1) * (x - c), 0] : [0, box.y1 + (y - box.y1) * 0.2 - y]
|
|
2402
2431
|
);
|
|
2403
2432
|
}
|
|
2404
|
-
function mouthWiden(mesh, box) {
|
|
2405
|
-
const c =
|
|
2433
|
+
function mouthWiden(mesh, box, frame = mouthFrameOf(box)) {
|
|
2434
|
+
const c = frame.c;
|
|
2406
2435
|
return localWarp(import_format3.StandardParameter.MouthOpen, mesh, box, [0, 1], ([x], v) => [
|
|
2407
2436
|
v * (AMPLITUDE.mouthOpenWidth - 1) * (x - c),
|
|
2408
2437
|
0
|
|
@@ -3059,6 +3088,8 @@ function forearmPoseDeformer(role, arm) {
|
|
|
3059
3088
|
}
|
|
3060
3089
|
|
|
3061
3090
|
// src/auto-rig/roles.ts
|
|
3091
|
+
var LIP_ROLES = ["mouth_inner", "lip_lower", "lip_upper"];
|
|
3092
|
+
var isLipRole = (role) => LIP_ROLES.includes(role);
|
|
3062
3093
|
var ROLE_TABLE = [
|
|
3063
3094
|
{ role: "hair_back", family: "hair_back" },
|
|
3064
3095
|
{ role: "body", family: "body" },
|
|
@@ -3068,8 +3099,14 @@ var ROLE_TABLE = [
|
|
|
3068
3099
|
{ role: "blush_L", family: "face" },
|
|
3069
3100
|
{ role: "blush_R", family: "face" },
|
|
3070
3101
|
{ role: "nose", family: "nose" },
|
|
3071
|
-
{ role: "mouth", family: "mouth"
|
|
3102
|
+
{ role: "mouth", family: "mouth" },
|
|
3072
3103
|
{ role: "mouth_open", family: "mouth" },
|
|
3104
|
+
// The lip set, back to front: the interior, the lower lip's skin, the upper
|
|
3105
|
+
// lip and its line over both. It folds open instead of crossfading
|
|
3106
|
+
// (`mouth.ts`), so it stands in for `mouth`, never beside it.
|
|
3107
|
+
{ role: "mouth_inner", family: "mouth" },
|
|
3108
|
+
{ role: "lip_lower", family: "mouth" },
|
|
3109
|
+
{ role: "lip_upper", family: "mouth" },
|
|
3073
3110
|
{ role: "eye_L", family: "eye_L", required: true },
|
|
3074
3111
|
{ role: "eye_R", family: "eye_R", required: true },
|
|
3075
3112
|
{ role: "iris_L", family: "eye_L" },
|
|
@@ -3127,6 +3164,29 @@ function checkArmsHaveBody(roles) {
|
|
|
3127
3164
|
);
|
|
3128
3165
|
}
|
|
3129
3166
|
}
|
|
3167
|
+
function checkMouth(roles) {
|
|
3168
|
+
const has = new Set(roles);
|
|
3169
|
+
const present = LIP_ROLES.filter((r) => has.has(r));
|
|
3170
|
+
if (present.length === 0) {
|
|
3171
|
+
if (!has.has("mouth")) {
|
|
3172
|
+
throw new Error(
|
|
3173
|
+
`auto-rig: missing required role mouth (or the lip set ${LIP_ROLES.join(", ")})`
|
|
3174
|
+
);
|
|
3175
|
+
}
|
|
3176
|
+
return;
|
|
3177
|
+
}
|
|
3178
|
+
if (present.length < LIP_ROLES.length) {
|
|
3179
|
+
throw new Error(
|
|
3180
|
+
`auto-rig: the lip set is partial: missing ${LIP_ROLES.filter((r) => !has.has(r)).join(", ")}`
|
|
3181
|
+
);
|
|
3182
|
+
}
|
|
3183
|
+
const mixed = ["mouth", "mouth_open"].filter((r) => has.has(r));
|
|
3184
|
+
if (mixed.length > 0) {
|
|
3185
|
+
throw new Error(
|
|
3186
|
+
`auto-rig: the lip set cannot be mixed with ${mixed.join(", ")} (the lip set replaces the mouth drawings; use one or the other)`
|
|
3187
|
+
);
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3130
3190
|
function checkPosesHaveArms(roles) {
|
|
3131
3191
|
const has = new Set(roles);
|
|
3132
3192
|
for (const side of ["L", "R"]) {
|
|
@@ -3162,13 +3222,14 @@ function parseLayerRoles(fileNames) {
|
|
|
3162
3222
|
`auto-rig: missing required role${missing.length > 1 ? "s" : ""} ${missing.join(", ")}`
|
|
3163
3223
|
);
|
|
3164
3224
|
}
|
|
3225
|
+
checkMouth(seen.keys());
|
|
3165
3226
|
checkArmsHaveBody(seen.keys());
|
|
3166
3227
|
checkPosesHaveArms(seen.keys());
|
|
3167
3228
|
return out;
|
|
3168
3229
|
}
|
|
3169
3230
|
|
|
3170
3231
|
// src/auto-rig/generate.ts
|
|
3171
|
-
var
|
|
3232
|
+
var import_format10 = require("@ikijs/format");
|
|
3172
3233
|
|
|
3173
3234
|
// src/auto-rig/animations.ts
|
|
3174
3235
|
var import_format7 = require("@ikijs/format");
|
|
@@ -3471,6 +3532,14 @@ function checkLayers(layers, canvas) {
|
|
|
3471
3532
|
if (missing.length > 0) {
|
|
3472
3533
|
throw new Error(`auto-rig: missing required role(s) ${missing.join(", ")}`);
|
|
3473
3534
|
}
|
|
3535
|
+
checkMouth(seen);
|
|
3536
|
+
for (const l of layers) {
|
|
3537
|
+
if (l.rowRuns === void 0 && isLipRole(l.role)) {
|
|
3538
|
+
throw new Error(
|
|
3539
|
+
`auto-rig: layer "${l.fileName}": the lip set needs rowRuns (the fold reads the opening's rows off them; measure with createLayerSetMeasurer)`
|
|
3540
|
+
);
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3474
3543
|
checkArmsHaveBody(seen);
|
|
3475
3544
|
checkPosesHaveArms(seen);
|
|
3476
3545
|
}
|
|
@@ -3590,7 +3659,180 @@ function checkStrandEdges(strand, irises, hair) {
|
|
|
3590
3659
|
}
|
|
3591
3660
|
|
|
3592
3661
|
// src/auto-rig/context.ts
|
|
3662
|
+
var import_format9 = require("@ikijs/format");
|
|
3663
|
+
|
|
3664
|
+
// src/auto-rig/mouth.ts
|
|
3593
3665
|
var import_format8 = require("@ikijs/format");
|
|
3666
|
+
var UPPER_SHARE = 0.3;
|
|
3667
|
+
var MOUTH_KNOT_PX = 4;
|
|
3668
|
+
var isLipSet = (byRole) => LIP_ROLES.every((r) => byRole.has(r));
|
|
3669
|
+
function columnRuns(layer) {
|
|
3670
|
+
const cols = /* @__PURE__ */ new Map();
|
|
3671
|
+
(layer.rowRuns ?? []).forEach((runs, k) => {
|
|
3672
|
+
for (let i = 0; i < runs.length; i += 2) {
|
|
3673
|
+
for (let c = runs[i]; c < runs[i + 1]; c++) {
|
|
3674
|
+
const list = cols.get(c) ?? [];
|
|
3675
|
+
const last = list[list.length - 1];
|
|
3676
|
+
if (last !== void 0 && last[1] === k) last[1] = k + 1;
|
|
3677
|
+
else list.push([k, k + 1]);
|
|
3678
|
+
cols.set(c, list);
|
|
3679
|
+
}
|
|
3680
|
+
}
|
|
3681
|
+
});
|
|
3682
|
+
return cols;
|
|
3683
|
+
}
|
|
3684
|
+
var edgesOf = (layer, run) => ({
|
|
3685
|
+
top: layer.canvasH / 2 - (layer.bbox.y + run[0]),
|
|
3686
|
+
bottom: layer.canvasH / 2 - (layer.bbox.y + run[1])
|
|
3687
|
+
});
|
|
3688
|
+
function mouthOpening(byRole) {
|
|
3689
|
+
const inner = byRole.get("mouth_inner");
|
|
3690
|
+
const upper = byRole.get("lip_upper");
|
|
3691
|
+
const lower = byRole.get("lip_lower");
|
|
3692
|
+
const innerCols = columnRuns(inner);
|
|
3693
|
+
if (innerCols.size === 0) {
|
|
3694
|
+
throw new LayerGeometryError(
|
|
3695
|
+
`auto-rig: layer "${inner.fileName}": mouth_inner has no opaque pixel (alpha \u2265 128) to read the opening off`
|
|
3696
|
+
);
|
|
3697
|
+
}
|
|
3698
|
+
const upperCols = columnRuns(upper);
|
|
3699
|
+
const lowerCols = columnRuns(lower);
|
|
3700
|
+
const cols = [...innerCols.keys()];
|
|
3701
|
+
const x0 = Math.min(...cols);
|
|
3702
|
+
const x1 = Math.max(...cols);
|
|
3703
|
+
const read = (col) => {
|
|
3704
|
+
const runs = innerCols.get(col);
|
|
3705
|
+
if (runs === void 0) return void 0;
|
|
3706
|
+
const T = edgesOf(inner, runs[0]).top;
|
|
3707
|
+
const Bb = edgesOf(inner, runs[runs.length - 1]).bottom;
|
|
3708
|
+
const line = [...upperCols.get(col) ?? []].reverse().map((r) => edgesOf(upper, r)).find((e) => e.top >= T);
|
|
3709
|
+
const Tu = line?.bottom ?? T;
|
|
3710
|
+
const lineTop = line?.top ?? T;
|
|
3711
|
+
const skin = lowerCols.get(col)?.[0];
|
|
3712
|
+
return {
|
|
3713
|
+
T,
|
|
3714
|
+
Bb,
|
|
3715
|
+
Tu,
|
|
3716
|
+
lineTop,
|
|
3717
|
+
lineH: lineTop - Tu,
|
|
3718
|
+
Bl: skin === void 0 ? Bb : edgesOf(lower, skin).top,
|
|
3719
|
+
H: T - Bb,
|
|
3720
|
+
seam: T - UPPER_SHARE * (T - Bb)
|
|
3721
|
+
};
|
|
3722
|
+
};
|
|
3723
|
+
const reads = [];
|
|
3724
|
+
for (let c = x0; c <= x1; c++) {
|
|
3725
|
+
let r;
|
|
3726
|
+
for (let d = 0; (r = read(c - d) ?? read(c + d)) === void 0; d++) ;
|
|
3727
|
+
reads.push(r);
|
|
3728
|
+
}
|
|
3729
|
+
const base = (col) => reads[Math.min(x1, Math.max(x0, col)) - x0];
|
|
3730
|
+
const centre = Math.round((x0 + x1) / 2);
|
|
3731
|
+
const w = base(centre).lineH;
|
|
3732
|
+
return {
|
|
3733
|
+
x0,
|
|
3734
|
+
x1,
|
|
3735
|
+
centre,
|
|
3736
|
+
canvasW: inner.canvasW,
|
|
3737
|
+
w,
|
|
3738
|
+
at(col) {
|
|
3739
|
+
const b = base(col);
|
|
3740
|
+
return { ...b, overlap: Math.max(0, Math.min(w, b.lineH)) };
|
|
3741
|
+
}
|
|
3742
|
+
};
|
|
3743
|
+
}
|
|
3744
|
+
function mouthFold(role, opening) {
|
|
3745
|
+
return (col, y, v) => {
|
|
3746
|
+
if (v === 1) return 0;
|
|
3747
|
+
if (role !== "mouth_inner" && (col < opening.x0 || col > opening.x1))
|
|
3748
|
+
return 0;
|
|
3749
|
+
const c = opening.at(col);
|
|
3750
|
+
const k = 1 - v;
|
|
3751
|
+
switch (role) {
|
|
3752
|
+
case "lip_upper":
|
|
3753
|
+
return (c.seam - c.Tu) * k;
|
|
3754
|
+
case "lip_lower":
|
|
3755
|
+
return (c.seam + c.overlap - c.Bl) * k;
|
|
3756
|
+
case "mouth_inner":
|
|
3757
|
+
return (c.seam + c.overlap - y) * k;
|
|
3758
|
+
}
|
|
3759
|
+
};
|
|
3760
|
+
}
|
|
3761
|
+
function columnOfKnot(knot, opening, role) {
|
|
3762
|
+
const boundary = Math.round(knot + opening.canvasW / 2);
|
|
3763
|
+
const col = boundary === opening.x1 + 1 ? opening.x1 : boundary;
|
|
3764
|
+
if (col >= opening.x0 && col <= opening.x1) return col;
|
|
3765
|
+
return role === "mouth_inner" ? Math.min(opening.x1, Math.max(opening.x0, col)) : void 0;
|
|
3766
|
+
}
|
|
3767
|
+
function mouthFoldWarp(role, mesh, xs, box, opening) {
|
|
3768
|
+
const fold = mouthFold(role, opening);
|
|
3769
|
+
return localWarp(import_format8.StandardParameter.MouthOpen, mesh, box, [0, 1], (p, v, i) => {
|
|
3770
|
+
const col = columnOfKnot(xs[i % xs.length], opening, role);
|
|
3771
|
+
return [0, col === void 0 ? 0 : fold(col, p[1], v)];
|
|
3772
|
+
});
|
|
3773
|
+
}
|
|
3774
|
+
function mouthKnots(union, opening) {
|
|
3775
|
+
const half = opening.canvasW / 2;
|
|
3776
|
+
const step = MOUTH_KNOT_PX;
|
|
3777
|
+
const xs = [union.x0];
|
|
3778
|
+
while (xs[xs.length - 1] + step < union.x1 - 0.3 * step)
|
|
3779
|
+
xs.push(xs[xs.length - 1] + step);
|
|
3780
|
+
xs.push(union.x1);
|
|
3781
|
+
const fixed = /* @__PURE__ */ new Set([union.x0, union.x1]);
|
|
3782
|
+
for (const x of [opening.x0 - half, opening.x1 + 1 - half]) {
|
|
3783
|
+
fixed.add(x);
|
|
3784
|
+
if (xs.includes(x)) continue;
|
|
3785
|
+
const near = xs.findIndex((v) => Math.abs(v - x) < 0.3 * step);
|
|
3786
|
+
if (near >= 0 && !fixed.has(xs[near])) xs[near] = x;
|
|
3787
|
+
else xs.push(x);
|
|
3788
|
+
}
|
|
3789
|
+
xs.sort((p, q) => p - q);
|
|
3790
|
+
for (const x of xs) {
|
|
3791
|
+
if (!Number.isInteger(x + half)) {
|
|
3792
|
+
throw new Error(
|
|
3793
|
+
`auto-rig: mouth knot ${x} is not an integer canvas boundary`
|
|
3794
|
+
);
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
return xs;
|
|
3798
|
+
}
|
|
3799
|
+
function mouthMesh(box, knots) {
|
|
3800
|
+
const xs = [box.x0, ...knots.filter((k) => k > box.x0 && k < box.x1), box.x1];
|
|
3801
|
+
const rows = cellsFor(bh(box), MOUTH_KNOT_PX, 2, 40);
|
|
3802
|
+
const ys = Array.from(
|
|
3803
|
+
{ length: rows + 1 },
|
|
3804
|
+
(_, r) => box.y1 - bh(box) * r / rows
|
|
3805
|
+
);
|
|
3806
|
+
return { mesh: columnMesh(box, { xs, ys }), xs };
|
|
3807
|
+
}
|
|
3808
|
+
function buildMouthRig(byRole) {
|
|
3809
|
+
const opening = mouthOpening(byRole);
|
|
3810
|
+
const union = unionBoxes(LIP_ROLES.map((r) => boxOfLayer(byRole.get(r))));
|
|
3811
|
+
return {
|
|
3812
|
+
opening,
|
|
3813
|
+
frame: mouthFrameOf(union),
|
|
3814
|
+
knots: mouthKnots(union, opening),
|
|
3815
|
+
union
|
|
3816
|
+
};
|
|
3817
|
+
}
|
|
3818
|
+
function mouthAnchor(byRole) {
|
|
3819
|
+
const box = (role) => boxOfLayer(byRole.get(role));
|
|
3820
|
+
if (isLipSet(byRole)) {
|
|
3821
|
+
const opening = mouthOpening(byRole);
|
|
3822
|
+
return {
|
|
3823
|
+
at: {
|
|
3824
|
+
x: cx(box("mouth_inner")),
|
|
3825
|
+
y: opening.at(opening.centre).seam
|
|
3826
|
+
},
|
|
3827
|
+
box: unionBoxes(LIP_ROLES.map(box))
|
|
3828
|
+
};
|
|
3829
|
+
}
|
|
3830
|
+
const m = box("mouth");
|
|
3831
|
+
return {
|
|
3832
|
+
at: { x: cx(m), y: cy(m) },
|
|
3833
|
+
box: byRole.has("mouth_open") ? unionBoxes([m, box("mouth_open")]) : m
|
|
3834
|
+
};
|
|
3835
|
+
}
|
|
3594
3836
|
|
|
3595
3837
|
// src/auto-rig/head.ts
|
|
3596
3838
|
function outerEdge(runs, axisX, side) {
|
|
@@ -3670,7 +3912,7 @@ function buildHeadFrame(layers, opts) {
|
|
|
3670
3912
|
return ya === void 0 || yb === void 0 ? void 0 : (ya + yb) / 2;
|
|
3671
3913
|
};
|
|
3672
3914
|
const eyeY = pair("iris_L", "iris_R") ?? pair("eye_L", "eye_R");
|
|
3673
|
-
const mouthY =
|
|
3915
|
+
const mouthY = mouthAnchor(byRole).at.y;
|
|
3674
3916
|
const rowY = (r) => canvasH / 2 - (faceLayer.bbox.y + r + 0.5);
|
|
3675
3917
|
const raw = filledProfile(faceLayer.rowHalfWidths);
|
|
3676
3918
|
const profile = raw === null ? null : smoothProfile(raw);
|
|
@@ -4643,7 +4885,6 @@ function sideIrises(byRole) {
|
|
|
4643
4885
|
return pair === void 0 ? {} : { left: pair.left.box, right: pair.right.box };
|
|
4644
4886
|
}
|
|
4645
4887
|
function solveContext(frame, byRole, options, hasNose) {
|
|
4646
|
-
const box = (role) => boxOfLayer(byRole.get(role));
|
|
4647
4888
|
const row = (s, strand2, sideSign) => {
|
|
4648
4889
|
if (strand2 !== void 0) {
|
|
4649
4890
|
const lo = sideSign < 0 ? strand2.irisOuter : strand2.irisInner;
|
|
@@ -4692,8 +4933,7 @@ function solveContext(frame, byRole, options, hasNose) {
|
|
|
4692
4933
|
noseBox = nose.denseCore ? boxOfPixels(nose.denseCore, nose.canvasW, nose.canvasH) : boxOfLayer(nose);
|
|
4693
4934
|
noseAt = { x: cx(noseBox), y: cy(noseBox) };
|
|
4694
4935
|
}
|
|
4695
|
-
const
|
|
4696
|
-
const m = box("mouth");
|
|
4936
|
+
const mouth = mouthAnchor(byRole);
|
|
4697
4937
|
return {
|
|
4698
4938
|
frame,
|
|
4699
4939
|
left,
|
|
@@ -4705,8 +4945,8 @@ function solveContext(frame, byRole, options, hasNose) {
|
|
|
4705
4945
|
eyeBoxes: { left: eyes.left.box, right: eyes.right.box },
|
|
4706
4946
|
noseAt,
|
|
4707
4947
|
noseBox,
|
|
4708
|
-
mouthAt:
|
|
4709
|
-
mouthBox
|
|
4948
|
+
mouthAt: mouth.at,
|
|
4949
|
+
mouthBox: mouth.box
|
|
4710
4950
|
};
|
|
4711
4951
|
}
|
|
4712
4952
|
function renderedLander(turn, parts, grids, from = /* @__PURE__ */ new Map()) {
|
|
@@ -4722,7 +4962,7 @@ function renderedLander(turn, parts, grids, from = /* @__PURE__ */ new Map()) {
|
|
|
4722
4962
|
const family = roleSpec(role).family;
|
|
4723
4963
|
const part = parts.find((p) => p.id === role);
|
|
4724
4964
|
const g = grids.get(family);
|
|
4725
|
-
const warp = part?.warps?.find((w) => w.parameter ===
|
|
4965
|
+
const warp = part?.warps?.find((w) => w.parameter === import_format9.StandardParameter.AngleX);
|
|
4726
4966
|
if (part === void 0 || part.mesh === void 0 || !g && !warp) {
|
|
4727
4967
|
return x + field(role)(x, y, ax, 0)[0];
|
|
4728
4968
|
}
|
|
@@ -5519,6 +5759,7 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
5519
5759
|
hh: frame.hh,
|
|
5520
5760
|
pivots: [...arms.values()].map((g) => g.shoulder)
|
|
5521
5761
|
}) : void 0;
|
|
5762
|
+
const lips = isLipSet(byRole) ? buildMouthRig(byRole) : void 0;
|
|
5522
5763
|
const parts = [];
|
|
5523
5764
|
const reach = /* @__PURE__ */ new Map();
|
|
5524
5765
|
let regionOf = [];
|
|
@@ -5550,7 +5791,8 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
5550
5791
|
frame,
|
|
5551
5792
|
byRole,
|
|
5552
5793
|
style,
|
|
5553
|
-
landmarks.hairFrontGrid
|
|
5794
|
+
landmarks.hairFrontGrid,
|
|
5795
|
+
lips
|
|
5554
5796
|
);
|
|
5555
5797
|
if (built.region !== void 0) {
|
|
5556
5798
|
regionOf = built.region;
|
|
@@ -5607,7 +5849,7 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
5607
5849
|
}
|
|
5608
5850
|
const parameters = declareParameters(new Set(byRole.keys()));
|
|
5609
5851
|
const model = {
|
|
5610
|
-
version:
|
|
5852
|
+
version: import_format10.IKI_FORMAT_VERSION,
|
|
5611
5853
|
name: "auto-rigged",
|
|
5612
5854
|
canvas: { width: canvas.width, height: canvas.height },
|
|
5613
5855
|
parameters,
|
|
@@ -5617,7 +5859,7 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
5617
5859
|
expressions: defaultExpressions(new Set(parameters.map((p) => p.id))),
|
|
5618
5860
|
motions: defaultMotions(new Set(parameters.map((p) => p.id)))
|
|
5619
5861
|
};
|
|
5620
|
-
const valid = (0,
|
|
5862
|
+
const valid = (0, import_format10.parseIkiModel)(model);
|
|
5621
5863
|
if (hasNose && options.onTurnSolved) {
|
|
5622
5864
|
options.onTurnSolved(
|
|
5623
5865
|
turnReport(
|
|
@@ -5632,14 +5874,14 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
5632
5874
|
function turnWarps(mesh, b, fieldOf) {
|
|
5633
5875
|
return [
|
|
5634
5876
|
localWarp(
|
|
5635
|
-
|
|
5877
|
+
import_format10.StandardParameter.AngleX,
|
|
5636
5878
|
mesh,
|
|
5637
5879
|
b,
|
|
5638
5880
|
X_STOPS,
|
|
5639
5881
|
([x, y], v, i) => v === 0 ? [0, 0] : [fieldOf(i)(x, y, v, 0)[0], 0]
|
|
5640
5882
|
),
|
|
5641
5883
|
localWarp(
|
|
5642
|
-
|
|
5884
|
+
import_format10.StandardParameter.AngleY,
|
|
5643
5885
|
mesh,
|
|
5644
5886
|
b,
|
|
5645
5887
|
Y_STOPS,
|
|
@@ -5652,7 +5894,7 @@ function plateFields(turn, region, head) {
|
|
|
5652
5894
|
const neck = neckField(turn);
|
|
5653
5895
|
return (i) => region[i] === "ear" ? ear : region[i] === "neck" ? neck : head;
|
|
5654
5896
|
}
|
|
5655
|
-
function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
5897
|
+
function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
|
|
5656
5898
|
const box = (role) => boxOfLayer(byRole.get(role));
|
|
5657
5899
|
const part = {
|
|
5658
5900
|
id: spec.role,
|
|
@@ -5667,6 +5909,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5667
5909
|
const bindings = [];
|
|
5668
5910
|
let extra = [0, 0];
|
|
5669
5911
|
let mesh;
|
|
5912
|
+
let knotXs;
|
|
5670
5913
|
let region;
|
|
5671
5914
|
let headStart = 0;
|
|
5672
5915
|
if (spec.role === "face") {
|
|
@@ -5677,6 +5920,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5677
5920
|
headStart = fm.headStart;
|
|
5678
5921
|
} else if (spec.role === "hair_front") {
|
|
5679
5922
|
mesh = columnMesh(b, frontGrid);
|
|
5923
|
+
} else if (lips !== void 0 && isLipRole(spec.role)) {
|
|
5924
|
+
({ mesh, xs: knotXs } = mouthMesh(b, lips.knots));
|
|
5680
5925
|
} else if (spec.family !== "body") {
|
|
5681
5926
|
const cells = MESH_CELLS[spec.role] ?? FEATURE_MESH_CELLS;
|
|
5682
5927
|
const px = Math.min(cells.px, meshScale(spec.role, frame));
|
|
@@ -5687,7 +5932,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5687
5932
|
}
|
|
5688
5933
|
if (mesh !== void 0) part.mesh = mesh;
|
|
5689
5934
|
const side = spec.role.endsWith("_L") ? "L" : "R";
|
|
5690
|
-
const eyeOpen = side === "L" ?
|
|
5935
|
+
const eyeOpen = side === "L" ? import_format10.StandardParameter.EyeOpenLeft : import_format10.StandardParameter.EyeOpenRight;
|
|
5691
5936
|
const hh = frame.hh;
|
|
5692
5937
|
switch (spec.role) {
|
|
5693
5938
|
case "face":
|
|
@@ -5702,7 +5947,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5702
5947
|
headOwnRoll(byRole.has("body"))
|
|
5703
5948
|
),
|
|
5704
5949
|
localWarp(
|
|
5705
|
-
|
|
5950
|
+
import_format10.StandardParameter.Breath,
|
|
5706
5951
|
mesh,
|
|
5707
5952
|
b,
|
|
5708
5953
|
[0, 1],
|
|
@@ -5749,8 +5994,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5749
5994
|
if (byRole.has("mouth_open")) {
|
|
5750
5995
|
warps.push(mouthWiden(mesh, b));
|
|
5751
5996
|
bindings.push(
|
|
5752
|
-
{ parameter:
|
|
5753
|
-
{ parameter:
|
|
5997
|
+
{ parameter: import_format10.StandardParameter.MouthOpen, channel: "opacity", from: 1, to: 0 },
|
|
5998
|
+
{ parameter: import_format10.StandardParameter.MouthOpen, channel: "opacity", from: 1, to: 0 }
|
|
5754
5999
|
);
|
|
5755
6000
|
extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
|
|
5756
6001
|
} else {
|
|
@@ -5760,19 +6005,19 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5760
6005
|
);
|
|
5761
6006
|
bindings.push(
|
|
5762
6007
|
{
|
|
5763
|
-
parameter:
|
|
6008
|
+
parameter: import_format10.StandardParameter.MouthOpen,
|
|
5764
6009
|
channel: "scaleY",
|
|
5765
6010
|
from: 0,
|
|
5766
6011
|
to: roundTo(grow, 0.01)
|
|
5767
6012
|
},
|
|
5768
6013
|
{
|
|
5769
|
-
parameter:
|
|
6014
|
+
parameter: import_format10.StandardParameter.MouthOpen,
|
|
5770
6015
|
channel: "scaleX",
|
|
5771
6016
|
from: 0,
|
|
5772
6017
|
to: roundTo(AMPLITUDE.mouthOpenWidth - 1, 0.01)
|
|
5773
6018
|
},
|
|
5774
6019
|
{
|
|
5775
|
-
parameter:
|
|
6020
|
+
parameter: import_format10.StandardParameter.MouthOpen,
|
|
5776
6021
|
channel: "translateY",
|
|
5777
6022
|
from: 0,
|
|
5778
6023
|
to: -roundTo(grow * bh(b) / 2, 0.01)
|
|
@@ -5784,17 +6029,26 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5784
6029
|
case "mouth_open":
|
|
5785
6030
|
warps.push(mouthForm(mesh, b), mouthOpenGrow(mesh, b));
|
|
5786
6031
|
bindings.push({
|
|
5787
|
-
parameter:
|
|
6032
|
+
parameter: import_format10.StandardParameter.MouthOpen,
|
|
5788
6033
|
channel: "opacity",
|
|
5789
6034
|
from: 0,
|
|
5790
6035
|
to: 1
|
|
5791
6036
|
});
|
|
5792
6037
|
extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
|
|
5793
6038
|
break;
|
|
6039
|
+
case "mouth_inner":
|
|
6040
|
+
case "lip_lower":
|
|
6041
|
+
case "lip_upper":
|
|
6042
|
+
warps.push(
|
|
6043
|
+
mouthFoldWarp(spec.role, mesh, knotXs, b, lips.opening),
|
|
6044
|
+
mouthForm(mesh, b, lips.frame),
|
|
6045
|
+
mouthWiden(mesh, b, lips.frame)
|
|
6046
|
+
);
|
|
6047
|
+
break;
|
|
5794
6048
|
case "blush_L":
|
|
5795
6049
|
case "blush_R":
|
|
5796
6050
|
bindings.push({
|
|
5797
|
-
parameter:
|
|
6051
|
+
parameter: import_format10.StandardParameter.Cheek,
|
|
5798
6052
|
channel: "opacity",
|
|
5799
6053
|
from: AMPLITUDE.blushRest,
|
|
5800
6054
|
to: 1
|
|
@@ -5805,8 +6059,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5805
6059
|
if (byRole.has("hair_front")) {
|
|
5806
6060
|
const amp = style.sway * AMPLITUDE.sway * hh;
|
|
5807
6061
|
warps.push(
|
|
5808
|
-
hairSway(
|
|
5809
|
-
hairSway(
|
|
6062
|
+
hairSway(import_format10.StandardParameter.HairSwayX, mesh, b, amp),
|
|
6063
|
+
hairSway(import_format10.StandardParameter.HairSwayZ, mesh, b, amp)
|
|
5810
6064
|
);
|
|
5811
6065
|
}
|
|
5812
6066
|
warps.push(tiltHang(mesh, b, pivot, hairHangWeight(frame.chinY, b.y0)));
|
|
@@ -5847,29 +6101,6 @@ function hairFrontGrid(b, frame, boxes) {
|
|
|
5847
6101
|
);
|
|
5848
6102
|
return { xs, ys };
|
|
5849
6103
|
}
|
|
5850
|
-
function columnMesh(b, grid) {
|
|
5851
|
-
const { xs } = grid;
|
|
5852
|
-
const rows = grid.ys.length - 1;
|
|
5853
|
-
const vertices = [];
|
|
5854
|
-
const uvs = [];
|
|
5855
|
-
for (let r = 0; r <= rows; r++) {
|
|
5856
|
-
for (const x of xs) {
|
|
5857
|
-
const u = (x - b.x0) / bw(b);
|
|
5858
|
-
vertices.push(roundTo(u - 0.5, 1e-5), roundTo(0.5 - r / rows, 1e-5));
|
|
5859
|
-
uvs.push(roundTo(u, 1e-5), roundTo(r / rows, 1e-5));
|
|
5860
|
-
}
|
|
5861
|
-
}
|
|
5862
|
-
const n = xs.length;
|
|
5863
|
-
const indices = [];
|
|
5864
|
-
for (let r = 0; r < rows; r++) {
|
|
5865
|
-
for (let c = 0; c + 1 < n; c++) {
|
|
5866
|
-
const tl = r * n + c;
|
|
5867
|
-
const bl = tl + n;
|
|
5868
|
-
indices.push(bl, bl + 1, tl, tl, bl + 1, tl + 1);
|
|
5869
|
-
}
|
|
5870
|
-
}
|
|
5871
|
-
return { vertices, uvs, indices };
|
|
5872
|
-
}
|
|
5873
6104
|
function meshScale(role, frame) {
|
|
5874
6105
|
switch (role) {
|
|
5875
6106
|
case "hair_front":
|
|
@@ -5895,9 +6126,9 @@ function deformers(frame, grids, bodyWarp, arms, byRole) {
|
|
|
5895
6126
|
pivot: { x: roundTo(frame.axisX, 0.01), y: roundTo(frame.chinY, 0.01) },
|
|
5896
6127
|
bindings: [
|
|
5897
6128
|
// AngleZ is clockwise-positive (Live2D's); a rotation is CCW-positive.
|
|
5898
|
-
{ parameter:
|
|
6129
|
+
{ parameter: import_format10.StandardParameter.AngleZ, channel: "rotate", from: roll, to: -roll },
|
|
5899
6130
|
{
|
|
5900
|
-
parameter:
|
|
6131
|
+
parameter: import_format10.StandardParameter.Breath,
|
|
5901
6132
|
channel: "translateY",
|
|
5902
6133
|
from: 0,
|
|
5903
6134
|
to: roundTo(headOwnBreath(hasBody) * frame.hh, 0.01)
|
|
@@ -5915,8 +6146,8 @@ function deformers(frame, grids, bodyWarp, arms, byRole) {
|
|
|
5915
6146
|
points: g.lattice.points
|
|
5916
6147
|
},
|
|
5917
6148
|
warp2d: {
|
|
5918
|
-
parameter:
|
|
5919
|
-
parameterY:
|
|
6149
|
+
parameter: import_format10.StandardParameter.AngleX,
|
|
6150
|
+
parameterY: import_format10.StandardParameter.AngleY,
|
|
5920
6151
|
valuesX: X_STOPS,
|
|
5921
6152
|
valuesY: Y_STOPS,
|
|
5922
6153
|
keyforms2d: g.keyforms.map((offsets) => ({ offsets }))
|
|
@@ -5929,16 +6160,16 @@ function hairPhysics() {
|
|
|
5929
6160
|
return [
|
|
5930
6161
|
{
|
|
5931
6162
|
id: "hairSway",
|
|
5932
|
-
input: { parameter:
|
|
5933
|
-
output: { parameter:
|
|
6163
|
+
input: { parameter: import_format10.StandardParameter.AngleX, weight: 1 },
|
|
6164
|
+
output: { parameter: import_format10.StandardParameter.HairSwayX, scale: 5 },
|
|
5934
6165
|
mass: 1,
|
|
5935
6166
|
stiffness: 30,
|
|
5936
6167
|
damping: 3
|
|
5937
6168
|
},
|
|
5938
6169
|
{
|
|
5939
6170
|
id: "hairTilt",
|
|
5940
|
-
input: { parameter:
|
|
5941
|
-
output: { parameter:
|
|
6171
|
+
input: { parameter: import_format10.StandardParameter.AngleZ, weight: 1 },
|
|
6172
|
+
output: { parameter: import_format10.StandardParameter.HairSwayZ, scale: 8 },
|
|
5942
6173
|
mass: 1,
|
|
5943
6174
|
stiffness: 25,
|
|
5944
6175
|
damping: 2.5
|
|
@@ -5948,13 +6179,13 @@ function hairPhysics() {
|
|
|
5948
6179
|
function declareParameters(roles) {
|
|
5949
6180
|
const out = [];
|
|
5950
6181
|
const add = (id, name, min, max, def) => out.push({ id, name, min, max, default: def });
|
|
5951
|
-
add(
|
|
5952
|
-
add(
|
|
5953
|
-
add(
|
|
6182
|
+
add(import_format10.StandardParameter.AngleX, "Head Angle", -30, 30, 0);
|
|
6183
|
+
add(import_format10.StandardParameter.AngleY, "Head Angle Y", -30, 30, 0);
|
|
6184
|
+
add(import_format10.StandardParameter.AngleZ, "Head Angle Z", -30, 30, 0);
|
|
5954
6185
|
if (roles.has("body")) {
|
|
5955
|
-
add(
|
|
5956
|
-
add(
|
|
5957
|
-
add(
|
|
6186
|
+
add(import_format10.StandardParameter.BodyAngleX, "Body Angle X", -10, 10, 0);
|
|
6187
|
+
add(import_format10.StandardParameter.BodyAngleY, "Body Angle Y", -10, 10, 0);
|
|
6188
|
+
add(import_format10.StandardParameter.BodyAngleZ, "Body Angle Z", -10, 10, 0);
|
|
5958
6189
|
}
|
|
5959
6190
|
const addPose = (side, pose, angle) => {
|
|
5960
6191
|
add(pose, `Arm Pose ${side}`, 0, 1, 0);
|
|
@@ -5963,40 +6194,40 @@ function declareParameters(roles) {
|
|
|
5963
6194
|
const [armMin, armMax] = ARM_RANGE;
|
|
5964
6195
|
const [elbowMin, elbowMax] = ELBOW_RANGE;
|
|
5965
6196
|
if (roles.has("arm_L")) {
|
|
5966
|
-
add(
|
|
5967
|
-
add(
|
|
6197
|
+
add(import_format10.StandardParameter.ArmLeft, "Arm L", armMin, armMax, 0);
|
|
6198
|
+
add(import_format10.StandardParameter.ElbowLeft, "Elbow L", elbowMin, elbowMax, 0);
|
|
5968
6199
|
if (roles.has("forearm_pose_L"))
|
|
5969
|
-
addPose("L",
|
|
6200
|
+
addPose("L", import_format10.StandardParameter.ArmPoseLeft, import_format10.StandardParameter.ArmPoseAngleLeft);
|
|
5970
6201
|
}
|
|
5971
6202
|
if (roles.has("arm_R")) {
|
|
5972
|
-
add(
|
|
5973
|
-
add(
|
|
6203
|
+
add(import_format10.StandardParameter.ArmRight, "Arm R", armMin, armMax, 0);
|
|
6204
|
+
add(import_format10.StandardParameter.ElbowRight, "Elbow R", elbowMin, elbowMax, 0);
|
|
5974
6205
|
if (roles.has("forearm_pose_R"))
|
|
5975
|
-
addPose("R",
|
|
6206
|
+
addPose("R", import_format10.StandardParameter.ArmPoseRight, import_format10.StandardParameter.ArmPoseAngleRight);
|
|
5976
6207
|
}
|
|
5977
|
-
add(
|
|
5978
|
-
add(
|
|
6208
|
+
add(import_format10.StandardParameter.EyeOpenLeft, "Eye L", 0, 1, 1);
|
|
6209
|
+
add(import_format10.StandardParameter.EyeOpenRight, "Eye R", 0, 1, 1);
|
|
5979
6210
|
if ([...roles].some((r) => /^(iris|pupil|highlight)_/.test(r))) {
|
|
5980
|
-
add(
|
|
5981
|
-
add(
|
|
6211
|
+
add(import_format10.StandardParameter.EyeballX, "Gaze X", -1, 1, 0);
|
|
6212
|
+
add(import_format10.StandardParameter.EyeballY, "Gaze Y", -1, 1, 0);
|
|
5982
6213
|
}
|
|
5983
|
-
add(
|
|
5984
|
-
add(
|
|
6214
|
+
add(import_format10.StandardParameter.MouthOpen, "Mouth Open", 0, 1, 0);
|
|
6215
|
+
add(import_format10.StandardParameter.MouthForm, "Mouth Form", -1, 1, 0);
|
|
5985
6216
|
if (roles.has("brow_L")) {
|
|
5986
|
-
add(
|
|
5987
|
-
add(
|
|
6217
|
+
add(import_format10.StandardParameter.BrowLeftY, "Brow L Y", -1, 1, 0);
|
|
6218
|
+
add(import_format10.StandardParameter.BrowLeftAngle, "Brow L Angle", -1, 1, 0);
|
|
5988
6219
|
}
|
|
5989
6220
|
if (roles.has("brow_R")) {
|
|
5990
|
-
add(
|
|
5991
|
-
add(
|
|
6221
|
+
add(import_format10.StandardParameter.BrowRightY, "Brow R Y", -1, 1, 0);
|
|
6222
|
+
add(import_format10.StandardParameter.BrowRightAngle, "Brow R Angle", -1, 1, 0);
|
|
5992
6223
|
}
|
|
5993
6224
|
if (roles.has("blush_L") || roles.has("blush_R")) {
|
|
5994
|
-
add(
|
|
6225
|
+
add(import_format10.StandardParameter.Cheek, "Cheek", 0, 1, 0);
|
|
5995
6226
|
}
|
|
5996
|
-
add(
|
|
6227
|
+
add(import_format10.StandardParameter.Breath, "Breath", 0, 1, 0);
|
|
5997
6228
|
if (roles.has("hair_front")) {
|
|
5998
|
-
add(
|
|
5999
|
-
add(
|
|
6229
|
+
add(import_format10.StandardParameter.HairSwayX, "Hair Sway X", -SWAY_RANGE, SWAY_RANGE, 0);
|
|
6230
|
+
add(import_format10.StandardParameter.HairSwayZ, "Hair Sway Z", -SWAY_RANGE, SWAY_RANGE, 0);
|
|
6000
6231
|
}
|
|
6001
6232
|
return out;
|
|
6002
6233
|
}
|