@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.mjs
CHANGED
|
@@ -1567,7 +1567,10 @@ var ROW_RUN_ROLES = /* @__PURE__ */ new Set([
|
|
|
1567
1567
|
"arm_L",
|
|
1568
1568
|
"arm_R",
|
|
1569
1569
|
"forearm_pose_L",
|
|
1570
|
-
"forearm_pose_R"
|
|
1570
|
+
"forearm_pose_R",
|
|
1571
|
+
"mouth_inner",
|
|
1572
|
+
"lip_lower",
|
|
1573
|
+
"lip_upper"
|
|
1571
1574
|
]);
|
|
1572
1575
|
var REST_HIDDEN_ROLES = /* @__PURE__ */ new Set([
|
|
1573
1576
|
"forearm_pose_L",
|
|
@@ -2184,6 +2187,29 @@ function gridMesh(cols, rows) {
|
|
|
2184
2187
|
}
|
|
2185
2188
|
return { vertices, uvs, indices };
|
|
2186
2189
|
}
|
|
2190
|
+
function columnMesh(b, grid) {
|
|
2191
|
+
const { xs } = grid;
|
|
2192
|
+
const rows = grid.ys.length - 1;
|
|
2193
|
+
const vertices = [];
|
|
2194
|
+
const uvs = [];
|
|
2195
|
+
for (let r = 0; r <= rows; r++) {
|
|
2196
|
+
for (const x of xs) {
|
|
2197
|
+
const u = (x - b.x0) / bw(b);
|
|
2198
|
+
vertices.push(roundTo(u - 0.5, 1e-5), roundTo(0.5 - r / rows, 1e-5));
|
|
2199
|
+
uvs.push(roundTo(u, 1e-5), roundTo(r / rows, 1e-5));
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
const n = xs.length;
|
|
2203
|
+
const indices = [];
|
|
2204
|
+
for (let r = 0; r < rows; r++) {
|
|
2205
|
+
for (let c = 0; c + 1 < n; c++) {
|
|
2206
|
+
const tl = r * n + c;
|
|
2207
|
+
const bl = tl + n;
|
|
2208
|
+
indices.push(bl, bl + 1, tl, tl, bl + 1, tl + 1);
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
return { vertices, uvs, indices };
|
|
2212
|
+
}
|
|
2187
2213
|
function meshPoints(mesh, box) {
|
|
2188
2214
|
const out = [];
|
|
2189
2215
|
for (let i = 0; i < mesh.vertices.length; i += 2) {
|
|
@@ -2309,10 +2335,13 @@ function browBindings(side, reach) {
|
|
|
2309
2335
|
];
|
|
2310
2336
|
}
|
|
2311
2337
|
var BROW_TILT = 15;
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2338
|
+
var mouthFrameOf = (box) => ({
|
|
2339
|
+
c: cx(box),
|
|
2340
|
+
half: bw(box) / 2,
|
|
2341
|
+
amp: 0.3 * bh(box)
|
|
2342
|
+
});
|
|
2343
|
+
function mouthForm(mesh, box, frame = mouthFrameOf(box)) {
|
|
2344
|
+
const { c, half, amp: a } = frame;
|
|
2316
2345
|
return localWarp(P.MouthForm, mesh, box, [-1, 0, 1], ([x], v) => {
|
|
2317
2346
|
if (v === 0) return [0, 0];
|
|
2318
2347
|
const u = (x - c) / half;
|
|
@@ -2330,8 +2359,8 @@ function mouthOpenGrow(mesh, box) {
|
|
|
2330
2359
|
([x, y], v) => v === 1 ? [(AMPLITUDE.mouthOpenWidth - 1) * (x - c), 0] : [0, box.y1 + (y - box.y1) * 0.2 - y]
|
|
2331
2360
|
);
|
|
2332
2361
|
}
|
|
2333
|
-
function mouthWiden(mesh, box) {
|
|
2334
|
-
const c =
|
|
2362
|
+
function mouthWiden(mesh, box, frame = mouthFrameOf(box)) {
|
|
2363
|
+
const c = frame.c;
|
|
2335
2364
|
return localWarp(P.MouthOpen, mesh, box, [0, 1], ([x], v) => [
|
|
2336
2365
|
v * (AMPLITUDE.mouthOpenWidth - 1) * (x - c),
|
|
2337
2366
|
0
|
|
@@ -2990,6 +3019,8 @@ function forearmPoseDeformer(role, arm) {
|
|
|
2990
3019
|
}
|
|
2991
3020
|
|
|
2992
3021
|
// src/auto-rig/roles.ts
|
|
3022
|
+
var LIP_ROLES = ["mouth_inner", "lip_lower", "lip_upper"];
|
|
3023
|
+
var isLipRole = (role) => LIP_ROLES.includes(role);
|
|
2993
3024
|
var ROLE_TABLE = [
|
|
2994
3025
|
{ role: "hair_back", family: "hair_back" },
|
|
2995
3026
|
{ role: "body", family: "body" },
|
|
@@ -2999,8 +3030,14 @@ var ROLE_TABLE = [
|
|
|
2999
3030
|
{ role: "blush_L", family: "face" },
|
|
3000
3031
|
{ role: "blush_R", family: "face" },
|
|
3001
3032
|
{ role: "nose", family: "nose" },
|
|
3002
|
-
{ role: "mouth", family: "mouth"
|
|
3033
|
+
{ role: "mouth", family: "mouth" },
|
|
3003
3034
|
{ role: "mouth_open", family: "mouth" },
|
|
3035
|
+
// The lip set, back to front: the interior, the lower lip's skin, the upper
|
|
3036
|
+
// lip and its line over both. It folds open instead of crossfading
|
|
3037
|
+
// (`mouth.ts`), so it stands in for `mouth`, never beside it.
|
|
3038
|
+
{ role: "mouth_inner", family: "mouth" },
|
|
3039
|
+
{ role: "lip_lower", family: "mouth" },
|
|
3040
|
+
{ role: "lip_upper", family: "mouth" },
|
|
3004
3041
|
{ role: "eye_L", family: "eye_L", required: true },
|
|
3005
3042
|
{ role: "eye_R", family: "eye_R", required: true },
|
|
3006
3043
|
{ role: "iris_L", family: "eye_L" },
|
|
@@ -3058,6 +3095,29 @@ function checkArmsHaveBody(roles) {
|
|
|
3058
3095
|
);
|
|
3059
3096
|
}
|
|
3060
3097
|
}
|
|
3098
|
+
function checkMouth(roles) {
|
|
3099
|
+
const has = new Set(roles);
|
|
3100
|
+
const present = LIP_ROLES.filter((r) => has.has(r));
|
|
3101
|
+
if (present.length === 0) {
|
|
3102
|
+
if (!has.has("mouth")) {
|
|
3103
|
+
throw new Error(
|
|
3104
|
+
`auto-rig: missing required role mouth (or the lip set ${LIP_ROLES.join(", ")})`
|
|
3105
|
+
);
|
|
3106
|
+
}
|
|
3107
|
+
return;
|
|
3108
|
+
}
|
|
3109
|
+
if (present.length < LIP_ROLES.length) {
|
|
3110
|
+
throw new Error(
|
|
3111
|
+
`auto-rig: the lip set is partial: missing ${LIP_ROLES.filter((r) => !has.has(r)).join(", ")}`
|
|
3112
|
+
);
|
|
3113
|
+
}
|
|
3114
|
+
const mixed = ["mouth", "mouth_open"].filter((r) => has.has(r));
|
|
3115
|
+
if (mixed.length > 0) {
|
|
3116
|
+
throw new Error(
|
|
3117
|
+
`auto-rig: the lip set cannot be mixed with ${mixed.join(", ")} (the lip set replaces the mouth drawings; use one or the other)`
|
|
3118
|
+
);
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3061
3121
|
function checkPosesHaveArms(roles) {
|
|
3062
3122
|
const has = new Set(roles);
|
|
3063
3123
|
for (const side of ["L", "R"]) {
|
|
@@ -3093,6 +3153,7 @@ function parseLayerRoles(fileNames) {
|
|
|
3093
3153
|
`auto-rig: missing required role${missing.length > 1 ? "s" : ""} ${missing.join(", ")}`
|
|
3094
3154
|
);
|
|
3095
3155
|
}
|
|
3156
|
+
checkMouth(seen.keys());
|
|
3096
3157
|
checkArmsHaveBody(seen.keys());
|
|
3097
3158
|
checkPosesHaveArms(seen.keys());
|
|
3098
3159
|
return out;
|
|
@@ -3101,7 +3162,7 @@ function parseLayerRoles(fileNames) {
|
|
|
3101
3162
|
// src/auto-rig/generate.ts
|
|
3102
3163
|
import {
|
|
3103
3164
|
IKI_FORMAT_VERSION as IKI_FORMAT_VERSION2,
|
|
3104
|
-
StandardParameter as
|
|
3165
|
+
StandardParameter as P8,
|
|
3105
3166
|
parseIkiModel as parseIkiModel3
|
|
3106
3167
|
} from "@ikijs/format";
|
|
3107
3168
|
|
|
@@ -3408,6 +3469,14 @@ function checkLayers(layers, canvas) {
|
|
|
3408
3469
|
if (missing.length > 0) {
|
|
3409
3470
|
throw new Error(`auto-rig: missing required role(s) ${missing.join(", ")}`);
|
|
3410
3471
|
}
|
|
3472
|
+
checkMouth(seen);
|
|
3473
|
+
for (const l of layers) {
|
|
3474
|
+
if (l.rowRuns === void 0 && isLipRole(l.role)) {
|
|
3475
|
+
throw new Error(
|
|
3476
|
+
`auto-rig: layer "${l.fileName}": the lip set needs rowRuns (the fold reads the opening's rows off them; measure with createLayerSetMeasurer)`
|
|
3477
|
+
);
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3411
3480
|
checkArmsHaveBody(seen);
|
|
3412
3481
|
checkPosesHaveArms(seen);
|
|
3413
3482
|
}
|
|
@@ -3527,7 +3596,182 @@ function checkStrandEdges(strand, irises, hair) {
|
|
|
3527
3596
|
}
|
|
3528
3597
|
|
|
3529
3598
|
// src/auto-rig/context.ts
|
|
3530
|
-
import { StandardParameter as
|
|
3599
|
+
import { StandardParameter as P7 } from "@ikijs/format";
|
|
3600
|
+
|
|
3601
|
+
// src/auto-rig/mouth.ts
|
|
3602
|
+
import {
|
|
3603
|
+
StandardParameter as P6
|
|
3604
|
+
} from "@ikijs/format";
|
|
3605
|
+
var UPPER_SHARE = 0.3;
|
|
3606
|
+
var MOUTH_KNOT_PX = 4;
|
|
3607
|
+
var isLipSet = (byRole) => LIP_ROLES.every((r) => byRole.has(r));
|
|
3608
|
+
function columnRuns(layer) {
|
|
3609
|
+
const cols = /* @__PURE__ */ new Map();
|
|
3610
|
+
(layer.rowRuns ?? []).forEach((runs, k) => {
|
|
3611
|
+
for (let i = 0; i < runs.length; i += 2) {
|
|
3612
|
+
for (let c = runs[i]; c < runs[i + 1]; c++) {
|
|
3613
|
+
const list = cols.get(c) ?? [];
|
|
3614
|
+
const last = list[list.length - 1];
|
|
3615
|
+
if (last !== void 0 && last[1] === k) last[1] = k + 1;
|
|
3616
|
+
else list.push([k, k + 1]);
|
|
3617
|
+
cols.set(c, list);
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
});
|
|
3621
|
+
return cols;
|
|
3622
|
+
}
|
|
3623
|
+
var edgesOf = (layer, run) => ({
|
|
3624
|
+
top: layer.canvasH / 2 - (layer.bbox.y + run[0]),
|
|
3625
|
+
bottom: layer.canvasH / 2 - (layer.bbox.y + run[1])
|
|
3626
|
+
});
|
|
3627
|
+
function mouthOpening(byRole) {
|
|
3628
|
+
const inner = byRole.get("mouth_inner");
|
|
3629
|
+
const upper = byRole.get("lip_upper");
|
|
3630
|
+
const lower = byRole.get("lip_lower");
|
|
3631
|
+
const innerCols = columnRuns(inner);
|
|
3632
|
+
if (innerCols.size === 0) {
|
|
3633
|
+
throw new LayerGeometryError(
|
|
3634
|
+
`auto-rig: layer "${inner.fileName}": mouth_inner has no opaque pixel (alpha \u2265 128) to read the opening off`
|
|
3635
|
+
);
|
|
3636
|
+
}
|
|
3637
|
+
const upperCols = columnRuns(upper);
|
|
3638
|
+
const lowerCols = columnRuns(lower);
|
|
3639
|
+
const cols = [...innerCols.keys()];
|
|
3640
|
+
const x0 = Math.min(...cols);
|
|
3641
|
+
const x1 = Math.max(...cols);
|
|
3642
|
+
const read = (col) => {
|
|
3643
|
+
const runs = innerCols.get(col);
|
|
3644
|
+
if (runs === void 0) return void 0;
|
|
3645
|
+
const T = edgesOf(inner, runs[0]).top;
|
|
3646
|
+
const Bb = edgesOf(inner, runs[runs.length - 1]).bottom;
|
|
3647
|
+
const line = [...upperCols.get(col) ?? []].reverse().map((r) => edgesOf(upper, r)).find((e) => e.top >= T);
|
|
3648
|
+
const Tu = line?.bottom ?? T;
|
|
3649
|
+
const lineTop = line?.top ?? T;
|
|
3650
|
+
const skin = lowerCols.get(col)?.[0];
|
|
3651
|
+
return {
|
|
3652
|
+
T,
|
|
3653
|
+
Bb,
|
|
3654
|
+
Tu,
|
|
3655
|
+
lineTop,
|
|
3656
|
+
lineH: lineTop - Tu,
|
|
3657
|
+
Bl: skin === void 0 ? Bb : edgesOf(lower, skin).top,
|
|
3658
|
+
H: T - Bb,
|
|
3659
|
+
seam: T - UPPER_SHARE * (T - Bb)
|
|
3660
|
+
};
|
|
3661
|
+
};
|
|
3662
|
+
const reads = [];
|
|
3663
|
+
for (let c = x0; c <= x1; c++) {
|
|
3664
|
+
let r;
|
|
3665
|
+
for (let d = 0; (r = read(c - d) ?? read(c + d)) === void 0; d++) ;
|
|
3666
|
+
reads.push(r);
|
|
3667
|
+
}
|
|
3668
|
+
const base = (col) => reads[Math.min(x1, Math.max(x0, col)) - x0];
|
|
3669
|
+
const centre = Math.round((x0 + x1) / 2);
|
|
3670
|
+
const w = base(centre).lineH;
|
|
3671
|
+
return {
|
|
3672
|
+
x0,
|
|
3673
|
+
x1,
|
|
3674
|
+
centre,
|
|
3675
|
+
canvasW: inner.canvasW,
|
|
3676
|
+
w,
|
|
3677
|
+
at(col) {
|
|
3678
|
+
const b = base(col);
|
|
3679
|
+
return { ...b, overlap: Math.max(0, Math.min(w, b.lineH)) };
|
|
3680
|
+
}
|
|
3681
|
+
};
|
|
3682
|
+
}
|
|
3683
|
+
function mouthFold(role, opening) {
|
|
3684
|
+
return (col, y, v) => {
|
|
3685
|
+
if (v === 1) return 0;
|
|
3686
|
+
if (role !== "mouth_inner" && (col < opening.x0 || col > opening.x1))
|
|
3687
|
+
return 0;
|
|
3688
|
+
const c = opening.at(col);
|
|
3689
|
+
const k = 1 - v;
|
|
3690
|
+
switch (role) {
|
|
3691
|
+
case "lip_upper":
|
|
3692
|
+
return (c.seam - c.Tu) * k;
|
|
3693
|
+
case "lip_lower":
|
|
3694
|
+
return (c.seam + c.overlap - c.Bl) * k;
|
|
3695
|
+
case "mouth_inner":
|
|
3696
|
+
return (c.seam + c.overlap - y) * k;
|
|
3697
|
+
}
|
|
3698
|
+
};
|
|
3699
|
+
}
|
|
3700
|
+
function columnOfKnot(knot, opening, role) {
|
|
3701
|
+
const boundary = Math.round(knot + opening.canvasW / 2);
|
|
3702
|
+
const col = boundary === opening.x1 + 1 ? opening.x1 : boundary;
|
|
3703
|
+
if (col >= opening.x0 && col <= opening.x1) return col;
|
|
3704
|
+
return role === "mouth_inner" ? Math.min(opening.x1, Math.max(opening.x0, col)) : void 0;
|
|
3705
|
+
}
|
|
3706
|
+
function mouthFoldWarp(role, mesh, xs, box, opening) {
|
|
3707
|
+
const fold = mouthFold(role, opening);
|
|
3708
|
+
return localWarp(P6.MouthOpen, mesh, box, [0, 1], (p, v, i) => {
|
|
3709
|
+
const col = columnOfKnot(xs[i % xs.length], opening, role);
|
|
3710
|
+
return [0, col === void 0 ? 0 : fold(col, p[1], v)];
|
|
3711
|
+
});
|
|
3712
|
+
}
|
|
3713
|
+
function mouthKnots(union, opening) {
|
|
3714
|
+
const half = opening.canvasW / 2;
|
|
3715
|
+
const step = MOUTH_KNOT_PX;
|
|
3716
|
+
const xs = [union.x0];
|
|
3717
|
+
while (xs[xs.length - 1] + step < union.x1 - 0.3 * step)
|
|
3718
|
+
xs.push(xs[xs.length - 1] + step);
|
|
3719
|
+
xs.push(union.x1);
|
|
3720
|
+
const fixed = /* @__PURE__ */ new Set([union.x0, union.x1]);
|
|
3721
|
+
for (const x of [opening.x0 - half, opening.x1 + 1 - half]) {
|
|
3722
|
+
fixed.add(x);
|
|
3723
|
+
if (xs.includes(x)) continue;
|
|
3724
|
+
const near = xs.findIndex((v) => Math.abs(v - x) < 0.3 * step);
|
|
3725
|
+
if (near >= 0 && !fixed.has(xs[near])) xs[near] = x;
|
|
3726
|
+
else xs.push(x);
|
|
3727
|
+
}
|
|
3728
|
+
xs.sort((p, q) => p - q);
|
|
3729
|
+
for (const x of xs) {
|
|
3730
|
+
if (!Number.isInteger(x + half)) {
|
|
3731
|
+
throw new Error(
|
|
3732
|
+
`auto-rig: mouth knot ${x} is not an integer canvas boundary`
|
|
3733
|
+
);
|
|
3734
|
+
}
|
|
3735
|
+
}
|
|
3736
|
+
return xs;
|
|
3737
|
+
}
|
|
3738
|
+
function mouthMesh(box, knots) {
|
|
3739
|
+
const xs = [box.x0, ...knots.filter((k) => k > box.x0 && k < box.x1), box.x1];
|
|
3740
|
+
const rows = cellsFor(bh(box), MOUTH_KNOT_PX, 2, 40);
|
|
3741
|
+
const ys = Array.from(
|
|
3742
|
+
{ length: rows + 1 },
|
|
3743
|
+
(_, r) => box.y1 - bh(box) * r / rows
|
|
3744
|
+
);
|
|
3745
|
+
return { mesh: columnMesh(box, { xs, ys }), xs };
|
|
3746
|
+
}
|
|
3747
|
+
function buildMouthRig(byRole) {
|
|
3748
|
+
const opening = mouthOpening(byRole);
|
|
3749
|
+
const union = unionBoxes(LIP_ROLES.map((r) => boxOfLayer(byRole.get(r))));
|
|
3750
|
+
return {
|
|
3751
|
+
opening,
|
|
3752
|
+
frame: mouthFrameOf(union),
|
|
3753
|
+
knots: mouthKnots(union, opening),
|
|
3754
|
+
union
|
|
3755
|
+
};
|
|
3756
|
+
}
|
|
3757
|
+
function mouthAnchor(byRole) {
|
|
3758
|
+
const box = (role) => boxOfLayer(byRole.get(role));
|
|
3759
|
+
if (isLipSet(byRole)) {
|
|
3760
|
+
const opening = mouthOpening(byRole);
|
|
3761
|
+
return {
|
|
3762
|
+
at: {
|
|
3763
|
+
x: cx(box("mouth_inner")),
|
|
3764
|
+
y: opening.at(opening.centre).seam
|
|
3765
|
+
},
|
|
3766
|
+
box: unionBoxes(LIP_ROLES.map(box))
|
|
3767
|
+
};
|
|
3768
|
+
}
|
|
3769
|
+
const m = box("mouth");
|
|
3770
|
+
return {
|
|
3771
|
+
at: { x: cx(m), y: cy(m) },
|
|
3772
|
+
box: byRole.has("mouth_open") ? unionBoxes([m, box("mouth_open")]) : m
|
|
3773
|
+
};
|
|
3774
|
+
}
|
|
3531
3775
|
|
|
3532
3776
|
// src/auto-rig/head.ts
|
|
3533
3777
|
function outerEdge(runs, axisX, side) {
|
|
@@ -3607,7 +3851,7 @@ function buildHeadFrame(layers, opts) {
|
|
|
3607
3851
|
return ya === void 0 || yb === void 0 ? void 0 : (ya + yb) / 2;
|
|
3608
3852
|
};
|
|
3609
3853
|
const eyeY = pair("iris_L", "iris_R") ?? pair("eye_L", "eye_R");
|
|
3610
|
-
const mouthY =
|
|
3854
|
+
const mouthY = mouthAnchor(byRole).at.y;
|
|
3611
3855
|
const rowY = (r) => canvasH / 2 - (faceLayer.bbox.y + r + 0.5);
|
|
3612
3856
|
const raw = filledProfile(faceLayer.rowHalfWidths);
|
|
3613
3857
|
const profile = raw === null ? null : smoothProfile(raw);
|
|
@@ -4580,7 +4824,6 @@ function sideIrises(byRole) {
|
|
|
4580
4824
|
return pair === void 0 ? {} : { left: pair.left.box, right: pair.right.box };
|
|
4581
4825
|
}
|
|
4582
4826
|
function solveContext(frame, byRole, options, hasNose) {
|
|
4583
|
-
const box = (role) => boxOfLayer(byRole.get(role));
|
|
4584
4827
|
const row = (s, strand2, sideSign) => {
|
|
4585
4828
|
if (strand2 !== void 0) {
|
|
4586
4829
|
const lo = sideSign < 0 ? strand2.irisOuter : strand2.irisInner;
|
|
@@ -4629,8 +4872,7 @@ function solveContext(frame, byRole, options, hasNose) {
|
|
|
4629
4872
|
noseBox = nose.denseCore ? boxOfPixels(nose.denseCore, nose.canvasW, nose.canvasH) : boxOfLayer(nose);
|
|
4630
4873
|
noseAt = { x: cx(noseBox), y: cy(noseBox) };
|
|
4631
4874
|
}
|
|
4632
|
-
const
|
|
4633
|
-
const m = box("mouth");
|
|
4875
|
+
const mouth = mouthAnchor(byRole);
|
|
4634
4876
|
return {
|
|
4635
4877
|
frame,
|
|
4636
4878
|
left,
|
|
@@ -4642,8 +4884,8 @@ function solveContext(frame, byRole, options, hasNose) {
|
|
|
4642
4884
|
eyeBoxes: { left: eyes.left.box, right: eyes.right.box },
|
|
4643
4885
|
noseAt,
|
|
4644
4886
|
noseBox,
|
|
4645
|
-
mouthAt:
|
|
4646
|
-
mouthBox
|
|
4887
|
+
mouthAt: mouth.at,
|
|
4888
|
+
mouthBox: mouth.box
|
|
4647
4889
|
};
|
|
4648
4890
|
}
|
|
4649
4891
|
function renderedLander(turn, parts, grids, from = /* @__PURE__ */ new Map()) {
|
|
@@ -4659,7 +4901,7 @@ function renderedLander(turn, parts, grids, from = /* @__PURE__ */ new Map()) {
|
|
|
4659
4901
|
const family = roleSpec(role).family;
|
|
4660
4902
|
const part = parts.find((p) => p.id === role);
|
|
4661
4903
|
const g = grids.get(family);
|
|
4662
|
-
const warp = part?.warps?.find((w) => w.parameter ===
|
|
4904
|
+
const warp = part?.warps?.find((w) => w.parameter === P7.AngleX);
|
|
4663
4905
|
if (part === void 0 || part.mesh === void 0 || !g && !warp) {
|
|
4664
4906
|
return x + field(role)(x, y, ax, 0)[0];
|
|
4665
4907
|
}
|
|
@@ -5456,6 +5698,7 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
5456
5698
|
hh: frame.hh,
|
|
5457
5699
|
pivots: [...arms.values()].map((g) => g.shoulder)
|
|
5458
5700
|
}) : void 0;
|
|
5701
|
+
const lips = isLipSet(byRole) ? buildMouthRig(byRole) : void 0;
|
|
5459
5702
|
const parts = [];
|
|
5460
5703
|
const reach = /* @__PURE__ */ new Map();
|
|
5461
5704
|
let regionOf = [];
|
|
@@ -5487,7 +5730,8 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
5487
5730
|
frame,
|
|
5488
5731
|
byRole,
|
|
5489
5732
|
style,
|
|
5490
|
-
landmarks.hairFrontGrid
|
|
5733
|
+
landmarks.hairFrontGrid,
|
|
5734
|
+
lips
|
|
5491
5735
|
);
|
|
5492
5736
|
if (built.region !== void 0) {
|
|
5493
5737
|
regionOf = built.region;
|
|
@@ -5569,14 +5813,14 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
5569
5813
|
function turnWarps(mesh, b, fieldOf) {
|
|
5570
5814
|
return [
|
|
5571
5815
|
localWarp(
|
|
5572
|
-
|
|
5816
|
+
P8.AngleX,
|
|
5573
5817
|
mesh,
|
|
5574
5818
|
b,
|
|
5575
5819
|
X_STOPS,
|
|
5576
5820
|
([x, y], v, i) => v === 0 ? [0, 0] : [fieldOf(i)(x, y, v, 0)[0], 0]
|
|
5577
5821
|
),
|
|
5578
5822
|
localWarp(
|
|
5579
|
-
|
|
5823
|
+
P8.AngleY,
|
|
5580
5824
|
mesh,
|
|
5581
5825
|
b,
|
|
5582
5826
|
Y_STOPS,
|
|
@@ -5589,7 +5833,7 @@ function plateFields(turn, region, head) {
|
|
|
5589
5833
|
const neck = neckField(turn);
|
|
5590
5834
|
return (i) => region[i] === "ear" ? ear : region[i] === "neck" ? neck : head;
|
|
5591
5835
|
}
|
|
5592
|
-
function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
5836
|
+
function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
|
|
5593
5837
|
const box = (role) => boxOfLayer(byRole.get(role));
|
|
5594
5838
|
const part = {
|
|
5595
5839
|
id: spec.role,
|
|
@@ -5604,6 +5848,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5604
5848
|
const bindings = [];
|
|
5605
5849
|
let extra = [0, 0];
|
|
5606
5850
|
let mesh;
|
|
5851
|
+
let knotXs;
|
|
5607
5852
|
let region;
|
|
5608
5853
|
let headStart = 0;
|
|
5609
5854
|
if (spec.role === "face") {
|
|
@@ -5614,6 +5859,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5614
5859
|
headStart = fm.headStart;
|
|
5615
5860
|
} else if (spec.role === "hair_front") {
|
|
5616
5861
|
mesh = columnMesh(b, frontGrid);
|
|
5862
|
+
} else if (lips !== void 0 && isLipRole(spec.role)) {
|
|
5863
|
+
({ mesh, xs: knotXs } = mouthMesh(b, lips.knots));
|
|
5617
5864
|
} else if (spec.family !== "body") {
|
|
5618
5865
|
const cells = MESH_CELLS[spec.role] ?? FEATURE_MESH_CELLS;
|
|
5619
5866
|
const px = Math.min(cells.px, meshScale(spec.role, frame));
|
|
@@ -5624,7 +5871,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5624
5871
|
}
|
|
5625
5872
|
if (mesh !== void 0) part.mesh = mesh;
|
|
5626
5873
|
const side = spec.role.endsWith("_L") ? "L" : "R";
|
|
5627
|
-
const eyeOpen = side === "L" ?
|
|
5874
|
+
const eyeOpen = side === "L" ? P8.EyeOpenLeft : P8.EyeOpenRight;
|
|
5628
5875
|
const hh = frame.hh;
|
|
5629
5876
|
switch (spec.role) {
|
|
5630
5877
|
case "face":
|
|
@@ -5639,7 +5886,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5639
5886
|
headOwnRoll(byRole.has("body"))
|
|
5640
5887
|
),
|
|
5641
5888
|
localWarp(
|
|
5642
|
-
|
|
5889
|
+
P8.Breath,
|
|
5643
5890
|
mesh,
|
|
5644
5891
|
b,
|
|
5645
5892
|
[0, 1],
|
|
@@ -5686,8 +5933,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5686
5933
|
if (byRole.has("mouth_open")) {
|
|
5687
5934
|
warps.push(mouthWiden(mesh, b));
|
|
5688
5935
|
bindings.push(
|
|
5689
|
-
{ parameter:
|
|
5690
|
-
{ parameter:
|
|
5936
|
+
{ parameter: P8.MouthOpen, channel: "opacity", from: 1, to: 0 },
|
|
5937
|
+
{ parameter: P8.MouthOpen, channel: "opacity", from: 1, to: 0 }
|
|
5691
5938
|
);
|
|
5692
5939
|
extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
|
|
5693
5940
|
} else {
|
|
@@ -5697,19 +5944,19 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5697
5944
|
);
|
|
5698
5945
|
bindings.push(
|
|
5699
5946
|
{
|
|
5700
|
-
parameter:
|
|
5947
|
+
parameter: P8.MouthOpen,
|
|
5701
5948
|
channel: "scaleY",
|
|
5702
5949
|
from: 0,
|
|
5703
5950
|
to: roundTo(grow, 0.01)
|
|
5704
5951
|
},
|
|
5705
5952
|
{
|
|
5706
|
-
parameter:
|
|
5953
|
+
parameter: P8.MouthOpen,
|
|
5707
5954
|
channel: "scaleX",
|
|
5708
5955
|
from: 0,
|
|
5709
5956
|
to: roundTo(AMPLITUDE.mouthOpenWidth - 1, 0.01)
|
|
5710
5957
|
},
|
|
5711
5958
|
{
|
|
5712
|
-
parameter:
|
|
5959
|
+
parameter: P8.MouthOpen,
|
|
5713
5960
|
channel: "translateY",
|
|
5714
5961
|
from: 0,
|
|
5715
5962
|
to: -roundTo(grow * bh(b) / 2, 0.01)
|
|
@@ -5721,17 +5968,26 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5721
5968
|
case "mouth_open":
|
|
5722
5969
|
warps.push(mouthForm(mesh, b), mouthOpenGrow(mesh, b));
|
|
5723
5970
|
bindings.push({
|
|
5724
|
-
parameter:
|
|
5971
|
+
parameter: P8.MouthOpen,
|
|
5725
5972
|
channel: "opacity",
|
|
5726
5973
|
from: 0,
|
|
5727
5974
|
to: 1
|
|
5728
5975
|
});
|
|
5729
5976
|
extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
|
|
5730
5977
|
break;
|
|
5978
|
+
case "mouth_inner":
|
|
5979
|
+
case "lip_lower":
|
|
5980
|
+
case "lip_upper":
|
|
5981
|
+
warps.push(
|
|
5982
|
+
mouthFoldWarp(spec.role, mesh, knotXs, b, lips.opening),
|
|
5983
|
+
mouthForm(mesh, b, lips.frame),
|
|
5984
|
+
mouthWiden(mesh, b, lips.frame)
|
|
5985
|
+
);
|
|
5986
|
+
break;
|
|
5731
5987
|
case "blush_L":
|
|
5732
5988
|
case "blush_R":
|
|
5733
5989
|
bindings.push({
|
|
5734
|
-
parameter:
|
|
5990
|
+
parameter: P8.Cheek,
|
|
5735
5991
|
channel: "opacity",
|
|
5736
5992
|
from: AMPLITUDE.blushRest,
|
|
5737
5993
|
to: 1
|
|
@@ -5742,8 +5998,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5742
5998
|
if (byRole.has("hair_front")) {
|
|
5743
5999
|
const amp = style.sway * AMPLITUDE.sway * hh;
|
|
5744
6000
|
warps.push(
|
|
5745
|
-
hairSway(
|
|
5746
|
-
hairSway(
|
|
6001
|
+
hairSway(P8.HairSwayX, mesh, b, amp),
|
|
6002
|
+
hairSway(P8.HairSwayZ, mesh, b, amp)
|
|
5747
6003
|
);
|
|
5748
6004
|
}
|
|
5749
6005
|
warps.push(tiltHang(mesh, b, pivot, hairHangWeight(frame.chinY, b.y0)));
|
|
@@ -5784,29 +6040,6 @@ function hairFrontGrid(b, frame, boxes) {
|
|
|
5784
6040
|
);
|
|
5785
6041
|
return { xs, ys };
|
|
5786
6042
|
}
|
|
5787
|
-
function columnMesh(b, grid) {
|
|
5788
|
-
const { xs } = grid;
|
|
5789
|
-
const rows = grid.ys.length - 1;
|
|
5790
|
-
const vertices = [];
|
|
5791
|
-
const uvs = [];
|
|
5792
|
-
for (let r = 0; r <= rows; r++) {
|
|
5793
|
-
for (const x of xs) {
|
|
5794
|
-
const u = (x - b.x0) / bw(b);
|
|
5795
|
-
vertices.push(roundTo(u - 0.5, 1e-5), roundTo(0.5 - r / rows, 1e-5));
|
|
5796
|
-
uvs.push(roundTo(u, 1e-5), roundTo(r / rows, 1e-5));
|
|
5797
|
-
}
|
|
5798
|
-
}
|
|
5799
|
-
const n = xs.length;
|
|
5800
|
-
const indices = [];
|
|
5801
|
-
for (let r = 0; r < rows; r++) {
|
|
5802
|
-
for (let c = 0; c + 1 < n; c++) {
|
|
5803
|
-
const tl = r * n + c;
|
|
5804
|
-
const bl = tl + n;
|
|
5805
|
-
indices.push(bl, bl + 1, tl, tl, bl + 1, tl + 1);
|
|
5806
|
-
}
|
|
5807
|
-
}
|
|
5808
|
-
return { vertices, uvs, indices };
|
|
5809
|
-
}
|
|
5810
6043
|
function meshScale(role, frame) {
|
|
5811
6044
|
switch (role) {
|
|
5812
6045
|
case "hair_front":
|
|
@@ -5832,9 +6065,9 @@ function deformers(frame, grids, bodyWarp, arms, byRole) {
|
|
|
5832
6065
|
pivot: { x: roundTo(frame.axisX, 0.01), y: roundTo(frame.chinY, 0.01) },
|
|
5833
6066
|
bindings: [
|
|
5834
6067
|
// AngleZ is clockwise-positive (Live2D's); a rotation is CCW-positive.
|
|
5835
|
-
{ parameter:
|
|
6068
|
+
{ parameter: P8.AngleZ, channel: "rotate", from: roll, to: -roll },
|
|
5836
6069
|
{
|
|
5837
|
-
parameter:
|
|
6070
|
+
parameter: P8.Breath,
|
|
5838
6071
|
channel: "translateY",
|
|
5839
6072
|
from: 0,
|
|
5840
6073
|
to: roundTo(headOwnBreath(hasBody) * frame.hh, 0.01)
|
|
@@ -5852,8 +6085,8 @@ function deformers(frame, grids, bodyWarp, arms, byRole) {
|
|
|
5852
6085
|
points: g.lattice.points
|
|
5853
6086
|
},
|
|
5854
6087
|
warp2d: {
|
|
5855
|
-
parameter:
|
|
5856
|
-
parameterY:
|
|
6088
|
+
parameter: P8.AngleX,
|
|
6089
|
+
parameterY: P8.AngleY,
|
|
5857
6090
|
valuesX: X_STOPS,
|
|
5858
6091
|
valuesY: Y_STOPS,
|
|
5859
6092
|
keyforms2d: g.keyforms.map((offsets) => ({ offsets }))
|
|
@@ -5866,16 +6099,16 @@ function hairPhysics() {
|
|
|
5866
6099
|
return [
|
|
5867
6100
|
{
|
|
5868
6101
|
id: "hairSway",
|
|
5869
|
-
input: { parameter:
|
|
5870
|
-
output: { parameter:
|
|
6102
|
+
input: { parameter: P8.AngleX, weight: 1 },
|
|
6103
|
+
output: { parameter: P8.HairSwayX, scale: 5 },
|
|
5871
6104
|
mass: 1,
|
|
5872
6105
|
stiffness: 30,
|
|
5873
6106
|
damping: 3
|
|
5874
6107
|
},
|
|
5875
6108
|
{
|
|
5876
6109
|
id: "hairTilt",
|
|
5877
|
-
input: { parameter:
|
|
5878
|
-
output: { parameter:
|
|
6110
|
+
input: { parameter: P8.AngleZ, weight: 1 },
|
|
6111
|
+
output: { parameter: P8.HairSwayZ, scale: 8 },
|
|
5879
6112
|
mass: 1,
|
|
5880
6113
|
stiffness: 25,
|
|
5881
6114
|
damping: 2.5
|
|
@@ -5885,13 +6118,13 @@ function hairPhysics() {
|
|
|
5885
6118
|
function declareParameters(roles) {
|
|
5886
6119
|
const out = [];
|
|
5887
6120
|
const add = (id, name, min, max, def) => out.push({ id, name, min, max, default: def });
|
|
5888
|
-
add(
|
|
5889
|
-
add(
|
|
5890
|
-
add(
|
|
6121
|
+
add(P8.AngleX, "Head Angle", -30, 30, 0);
|
|
6122
|
+
add(P8.AngleY, "Head Angle Y", -30, 30, 0);
|
|
6123
|
+
add(P8.AngleZ, "Head Angle Z", -30, 30, 0);
|
|
5891
6124
|
if (roles.has("body")) {
|
|
5892
|
-
add(
|
|
5893
|
-
add(
|
|
5894
|
-
add(
|
|
6125
|
+
add(P8.BodyAngleX, "Body Angle X", -10, 10, 0);
|
|
6126
|
+
add(P8.BodyAngleY, "Body Angle Y", -10, 10, 0);
|
|
6127
|
+
add(P8.BodyAngleZ, "Body Angle Z", -10, 10, 0);
|
|
5895
6128
|
}
|
|
5896
6129
|
const addPose = (side, pose, angle) => {
|
|
5897
6130
|
add(pose, `Arm Pose ${side}`, 0, 1, 0);
|
|
@@ -5900,40 +6133,40 @@ function declareParameters(roles) {
|
|
|
5900
6133
|
const [armMin, armMax] = ARM_RANGE;
|
|
5901
6134
|
const [elbowMin, elbowMax] = ELBOW_RANGE;
|
|
5902
6135
|
if (roles.has("arm_L")) {
|
|
5903
|
-
add(
|
|
5904
|
-
add(
|
|
6136
|
+
add(P8.ArmLeft, "Arm L", armMin, armMax, 0);
|
|
6137
|
+
add(P8.ElbowLeft, "Elbow L", elbowMin, elbowMax, 0);
|
|
5905
6138
|
if (roles.has("forearm_pose_L"))
|
|
5906
|
-
addPose("L",
|
|
6139
|
+
addPose("L", P8.ArmPoseLeft, P8.ArmPoseAngleLeft);
|
|
5907
6140
|
}
|
|
5908
6141
|
if (roles.has("arm_R")) {
|
|
5909
|
-
add(
|
|
5910
|
-
add(
|
|
6142
|
+
add(P8.ArmRight, "Arm R", armMin, armMax, 0);
|
|
6143
|
+
add(P8.ElbowRight, "Elbow R", elbowMin, elbowMax, 0);
|
|
5911
6144
|
if (roles.has("forearm_pose_R"))
|
|
5912
|
-
addPose("R",
|
|
6145
|
+
addPose("R", P8.ArmPoseRight, P8.ArmPoseAngleRight);
|
|
5913
6146
|
}
|
|
5914
|
-
add(
|
|
5915
|
-
add(
|
|
6147
|
+
add(P8.EyeOpenLeft, "Eye L", 0, 1, 1);
|
|
6148
|
+
add(P8.EyeOpenRight, "Eye R", 0, 1, 1);
|
|
5916
6149
|
if ([...roles].some((r) => /^(iris|pupil|highlight)_/.test(r))) {
|
|
5917
|
-
add(
|
|
5918
|
-
add(
|
|
6150
|
+
add(P8.EyeballX, "Gaze X", -1, 1, 0);
|
|
6151
|
+
add(P8.EyeballY, "Gaze Y", -1, 1, 0);
|
|
5919
6152
|
}
|
|
5920
|
-
add(
|
|
5921
|
-
add(
|
|
6153
|
+
add(P8.MouthOpen, "Mouth Open", 0, 1, 0);
|
|
6154
|
+
add(P8.MouthForm, "Mouth Form", -1, 1, 0);
|
|
5922
6155
|
if (roles.has("brow_L")) {
|
|
5923
|
-
add(
|
|
5924
|
-
add(
|
|
6156
|
+
add(P8.BrowLeftY, "Brow L Y", -1, 1, 0);
|
|
6157
|
+
add(P8.BrowLeftAngle, "Brow L Angle", -1, 1, 0);
|
|
5925
6158
|
}
|
|
5926
6159
|
if (roles.has("brow_R")) {
|
|
5927
|
-
add(
|
|
5928
|
-
add(
|
|
6160
|
+
add(P8.BrowRightY, "Brow R Y", -1, 1, 0);
|
|
6161
|
+
add(P8.BrowRightAngle, "Brow R Angle", -1, 1, 0);
|
|
5929
6162
|
}
|
|
5930
6163
|
if (roles.has("blush_L") || roles.has("blush_R")) {
|
|
5931
|
-
add(
|
|
6164
|
+
add(P8.Cheek, "Cheek", 0, 1, 0);
|
|
5932
6165
|
}
|
|
5933
|
-
add(
|
|
6166
|
+
add(P8.Breath, "Breath", 0, 1, 0);
|
|
5934
6167
|
if (roles.has("hair_front")) {
|
|
5935
|
-
add(
|
|
5936
|
-
add(
|
|
6168
|
+
add(P8.HairSwayX, "Hair Sway X", -SWAY_RANGE, SWAY_RANGE, 0);
|
|
6169
|
+
add(P8.HairSwayZ, "Hair Sway Z", -SWAY_RANGE, SWAY_RANGE, 0);
|
|
5937
6170
|
}
|
|
5938
6171
|
return out;
|
|
5939
6172
|
}
|