@ikijs/editor 0.20.0 → 0.22.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 +14 -14
- package/dist/index.d.mts +140 -11
- package/dist/index.d.ts +140 -11
- package/dist/index.js +415 -116
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +410 -114
- 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
|
|
@@ -2989,7 +3018,14 @@ function forearmPoseDeformer(role, arm) {
|
|
|
2989
3018
|
};
|
|
2990
3019
|
}
|
|
2991
3020
|
|
|
3021
|
+
// src/auto-rig/mouth.ts
|
|
3022
|
+
import {
|
|
3023
|
+
StandardParameter as P5
|
|
3024
|
+
} from "@ikijs/format";
|
|
3025
|
+
|
|
2992
3026
|
// src/auto-rig/roles.ts
|
|
3027
|
+
var LIP_ROLES = ["mouth_inner", "lip_lower", "lip_upper"];
|
|
3028
|
+
var isLipRole = (role) => LIP_ROLES.includes(role);
|
|
2993
3029
|
var ROLE_TABLE = [
|
|
2994
3030
|
{ role: "hair_back", family: "hair_back" },
|
|
2995
3031
|
{ role: "body", family: "body" },
|
|
@@ -2999,8 +3035,14 @@ var ROLE_TABLE = [
|
|
|
2999
3035
|
{ role: "blush_L", family: "face" },
|
|
3000
3036
|
{ role: "blush_R", family: "face" },
|
|
3001
3037
|
{ role: "nose", family: "nose" },
|
|
3002
|
-
{ role: "mouth", family: "mouth"
|
|
3038
|
+
{ role: "mouth", family: "mouth" },
|
|
3003
3039
|
{ role: "mouth_open", family: "mouth" },
|
|
3040
|
+
// The lip set, back to front: the interior, the lower lip's skin, the upper
|
|
3041
|
+
// lip and its line over both. It folds open instead of crossfading
|
|
3042
|
+
// (`mouth.ts`), so it stands in for `mouth`, never beside it.
|
|
3043
|
+
{ role: "mouth_inner", family: "mouth" },
|
|
3044
|
+
{ role: "lip_lower", family: "mouth" },
|
|
3045
|
+
{ role: "lip_upper", family: "mouth" },
|
|
3004
3046
|
{ role: "eye_L", family: "eye_L", required: true },
|
|
3005
3047
|
{ role: "eye_R", family: "eye_R", required: true },
|
|
3006
3048
|
{ role: "iris_L", family: "eye_L" },
|
|
@@ -3058,6 +3100,29 @@ function checkArmsHaveBody(roles) {
|
|
|
3058
3100
|
);
|
|
3059
3101
|
}
|
|
3060
3102
|
}
|
|
3103
|
+
function checkMouth(roles) {
|
|
3104
|
+
const has = new Set(roles);
|
|
3105
|
+
const present = LIP_ROLES.filter((r) => has.has(r));
|
|
3106
|
+
if (present.length === 0) {
|
|
3107
|
+
if (!has.has("mouth")) {
|
|
3108
|
+
throw new Error(
|
|
3109
|
+
`auto-rig: missing required role mouth (or the lip set ${LIP_ROLES.join(", ")})`
|
|
3110
|
+
);
|
|
3111
|
+
}
|
|
3112
|
+
return;
|
|
3113
|
+
}
|
|
3114
|
+
if (present.length < LIP_ROLES.length) {
|
|
3115
|
+
throw new Error(
|
|
3116
|
+
`auto-rig: the lip set is partial: missing ${LIP_ROLES.filter((r) => !has.has(r)).join(", ")}`
|
|
3117
|
+
);
|
|
3118
|
+
}
|
|
3119
|
+
const mixed = ["mouth", "mouth_open"].filter((r) => has.has(r));
|
|
3120
|
+
if (mixed.length > 0) {
|
|
3121
|
+
throw new Error(
|
|
3122
|
+
`auto-rig: the lip set cannot be mixed with ${mixed.join(", ")} (the lip set replaces the mouth drawings; use one or the other)`
|
|
3123
|
+
);
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3061
3126
|
function checkPosesHaveArms(roles) {
|
|
3062
3127
|
const has = new Set(roles);
|
|
3063
3128
|
for (const side of ["L", "R"]) {
|
|
@@ -3093,44 +3158,273 @@ function parseLayerRoles(fileNames) {
|
|
|
3093
3158
|
`auto-rig: missing required role${missing.length > 1 ? "s" : ""} ${missing.join(", ")}`
|
|
3094
3159
|
);
|
|
3095
3160
|
}
|
|
3161
|
+
checkMouth(seen.keys());
|
|
3096
3162
|
checkArmsHaveBody(seen.keys());
|
|
3097
3163
|
checkPosesHaveArms(seen.keys());
|
|
3098
3164
|
return out;
|
|
3099
3165
|
}
|
|
3100
3166
|
|
|
3167
|
+
// src/auto-rig/mouth.ts
|
|
3168
|
+
var UPPER_SHARE = 0.4;
|
|
3169
|
+
var MOUTH_KNOT_PX = 4;
|
|
3170
|
+
var isLipSet = (byRole) => LIP_ROLES.every((r) => byRole.has(r));
|
|
3171
|
+
function columnRuns(layer) {
|
|
3172
|
+
const cols = /* @__PURE__ */ new Map();
|
|
3173
|
+
(layer.rowRuns ?? []).forEach((runs, k) => {
|
|
3174
|
+
for (let i = 0; i < runs.length; i += 2) {
|
|
3175
|
+
for (let c = runs[i]; c < runs[i + 1]; c++) {
|
|
3176
|
+
const list = cols.get(c) ?? [];
|
|
3177
|
+
const last = list[list.length - 1];
|
|
3178
|
+
if (last !== void 0 && last[1] === k) last[1] = k + 1;
|
|
3179
|
+
else list.push([k, k + 1]);
|
|
3180
|
+
cols.set(c, list);
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
});
|
|
3184
|
+
return cols;
|
|
3185
|
+
}
|
|
3186
|
+
var edgesOf = (layer, run) => ({
|
|
3187
|
+
top: layer.canvasH / 2 - (layer.bbox.y + run[0]),
|
|
3188
|
+
bottom: layer.canvasH / 2 - (layer.bbox.y + run[1])
|
|
3189
|
+
});
|
|
3190
|
+
function mouthOpening(byRole) {
|
|
3191
|
+
const inner = byRole.get("mouth_inner");
|
|
3192
|
+
const upper = byRole.get("lip_upper");
|
|
3193
|
+
const lower = byRole.get("lip_lower");
|
|
3194
|
+
const innerCols = columnRuns(inner);
|
|
3195
|
+
if (innerCols.size === 0) {
|
|
3196
|
+
throw new LayerGeometryError(
|
|
3197
|
+
`auto-rig: layer "${inner.fileName}": mouth_inner has no opaque pixel (alpha \u2265 128) to read the opening off`
|
|
3198
|
+
);
|
|
3199
|
+
}
|
|
3200
|
+
const upperCols = columnRuns(upper);
|
|
3201
|
+
const lowerCols = columnRuns(lower);
|
|
3202
|
+
const cols = [...innerCols.keys()];
|
|
3203
|
+
const x0 = Math.min(...cols);
|
|
3204
|
+
const x1 = Math.max(...cols);
|
|
3205
|
+
const read = (col) => {
|
|
3206
|
+
const runs = innerCols.get(col);
|
|
3207
|
+
if (runs === void 0) return void 0;
|
|
3208
|
+
const T = edgesOf(inner, runs[0]).top;
|
|
3209
|
+
const Bb = edgesOf(inner, runs[runs.length - 1]).bottom;
|
|
3210
|
+
const line = [...upperCols.get(col) ?? []].reverse().map((r) => edgesOf(upper, r)).find((e) => e.top >= T);
|
|
3211
|
+
const Tu = line?.bottom ?? T;
|
|
3212
|
+
const lineTop = line?.top ?? T;
|
|
3213
|
+
const skin = lowerCols.get(col)?.[0];
|
|
3214
|
+
return {
|
|
3215
|
+
T,
|
|
3216
|
+
Bb,
|
|
3217
|
+
Tu,
|
|
3218
|
+
lineTop,
|
|
3219
|
+
lineH: lineTop - Tu,
|
|
3220
|
+
Bl: skin === void 0 ? Bb : edgesOf(lower, skin).top,
|
|
3221
|
+
H: T - Bb
|
|
3222
|
+
};
|
|
3223
|
+
};
|
|
3224
|
+
const reads = [];
|
|
3225
|
+
for (let c = x0; c <= x1; c++) {
|
|
3226
|
+
let r;
|
|
3227
|
+
for (let d = 0; (r = read(c - d) ?? read(c + d)) === void 0; d++) ;
|
|
3228
|
+
reads.push(r);
|
|
3229
|
+
}
|
|
3230
|
+
const m = (x0 + x1) / 2;
|
|
3231
|
+
let p = 0;
|
|
3232
|
+
let q = 0;
|
|
3233
|
+
if (reads.length >= 4) {
|
|
3234
|
+
let s11 = 0;
|
|
3235
|
+
let s12 = 0;
|
|
3236
|
+
let s22 = 0;
|
|
3237
|
+
let t1 = 0;
|
|
3238
|
+
let t2 = 0;
|
|
3239
|
+
reads.forEach((r, i) => {
|
|
3240
|
+
const x = x0 + i;
|
|
3241
|
+
const b1 = (x - x0) * (x - x1);
|
|
3242
|
+
const b2 = b1 * (x - m);
|
|
3243
|
+
const d = r.T - UPPER_SHARE * r.H - r.Tu;
|
|
3244
|
+
s11 += b1 * b1;
|
|
3245
|
+
s12 += b1 * b2;
|
|
3246
|
+
s22 += b2 * b2;
|
|
3247
|
+
t1 += b1 * d;
|
|
3248
|
+
t2 += b2 * d;
|
|
3249
|
+
});
|
|
3250
|
+
const det = s11 * s22 - s12 * s12;
|
|
3251
|
+
p = (t1 * s22 - t2 * s12) / det;
|
|
3252
|
+
q = (s11 * t2 - s12 * t1) / det;
|
|
3253
|
+
}
|
|
3254
|
+
const columns = reads.map((r, i) => {
|
|
3255
|
+
const x = x0 + i;
|
|
3256
|
+
return { ...r, seam: r.Tu + (x - x0) * (x - x1) * (p + q * (x - m)) };
|
|
3257
|
+
});
|
|
3258
|
+
const base = (col) => columns[Math.min(x1, Math.max(x0, col)) - x0];
|
|
3259
|
+
const centre = Math.round((x0 + x1) / 2);
|
|
3260
|
+
const w = base(centre).lineH;
|
|
3261
|
+
return {
|
|
3262
|
+
x0,
|
|
3263
|
+
x1,
|
|
3264
|
+
centre,
|
|
3265
|
+
canvasW: inner.canvasW,
|
|
3266
|
+
w,
|
|
3267
|
+
at(col) {
|
|
3268
|
+
const b = base(col);
|
|
3269
|
+
return { ...b, overlap: Math.max(0, Math.min(w, b.lineH)) };
|
|
3270
|
+
}
|
|
3271
|
+
};
|
|
3272
|
+
}
|
|
3273
|
+
function mouthFold(role, opening) {
|
|
3274
|
+
return (col, y, v) => {
|
|
3275
|
+
if (v === 1) return 0;
|
|
3276
|
+
if (role !== "mouth_inner" && (col < opening.x0 || col > opening.x1))
|
|
3277
|
+
return 0;
|
|
3278
|
+
const c = opening.at(col);
|
|
3279
|
+
const k = 1 - v;
|
|
3280
|
+
switch (role) {
|
|
3281
|
+
case "lip_upper":
|
|
3282
|
+
return (c.seam - c.Tu) * k;
|
|
3283
|
+
case "lip_lower":
|
|
3284
|
+
return (c.seam + c.overlap - c.Bl) * k;
|
|
3285
|
+
case "mouth_inner":
|
|
3286
|
+
return (c.seam + c.overlap - y) * k;
|
|
3287
|
+
}
|
|
3288
|
+
};
|
|
3289
|
+
}
|
|
3290
|
+
function columnOfKnot(knot, opening, role) {
|
|
3291
|
+
const boundary = Math.round(knot + opening.canvasW / 2);
|
|
3292
|
+
const col = boundary === opening.x1 + 1 ? opening.x1 : boundary;
|
|
3293
|
+
if (col >= opening.x0 && col <= opening.x1) return col;
|
|
3294
|
+
return role === "mouth_inner" ? Math.min(opening.x1, Math.max(opening.x0, col)) : void 0;
|
|
3295
|
+
}
|
|
3296
|
+
function mouthFoldWarp(role, mesh, xs, box, opening) {
|
|
3297
|
+
const fold = mouthFold(role, opening);
|
|
3298
|
+
return localWarp(P5.MouthOpen, mesh, box, [0, 1], (p, v, i) => {
|
|
3299
|
+
const col = columnOfKnot(xs[i % xs.length], opening, role);
|
|
3300
|
+
return [0, col === void 0 ? 0 : fold(col, p[1], v)];
|
|
3301
|
+
});
|
|
3302
|
+
}
|
|
3303
|
+
function mouthRestShift(byRole) {
|
|
3304
|
+
const rig = buildMouthRig(byRole);
|
|
3305
|
+
const fields = /* @__PURE__ */ new Map();
|
|
3306
|
+
for (const role of ["lip_upper", "lip_lower"]) {
|
|
3307
|
+
const box = boxOfLayer(byRole.get(role));
|
|
3308
|
+
const { mesh, xs } = mouthMesh(box, rig.knots);
|
|
3309
|
+
const warp = mouthFoldWarp(role, mesh, xs, box, rig.opening);
|
|
3310
|
+
const offsets = warp.keyforms[0].offsets;
|
|
3311
|
+
fields.set(role, {
|
|
3312
|
+
box,
|
|
3313
|
+
xs,
|
|
3314
|
+
dy: xs.map((_, i) => offsets[2 * i + 1] * bh(box))
|
|
3315
|
+
});
|
|
3316
|
+
}
|
|
3317
|
+
const half = rig.opening.canvasW / 2;
|
|
3318
|
+
return (role, col) => {
|
|
3319
|
+
const { box, xs, dy } = fields.get(role);
|
|
3320
|
+
const x = col + 0.5 - half;
|
|
3321
|
+
if (x < box.x0 || x > box.x1) return 0;
|
|
3322
|
+
let k = 1;
|
|
3323
|
+
while (k < xs.length - 1 && xs[k] < x) k++;
|
|
3324
|
+
const t = (x - xs[k - 1]) / (xs[k] - xs[k - 1]);
|
|
3325
|
+
return dy[k - 1] + (dy[k] - dy[k - 1]) * t;
|
|
3326
|
+
};
|
|
3327
|
+
}
|
|
3328
|
+
function mouthKnots(union, opening) {
|
|
3329
|
+
const half = opening.canvasW / 2;
|
|
3330
|
+
const step = MOUTH_KNOT_PX;
|
|
3331
|
+
const xs = [union.x0];
|
|
3332
|
+
while (xs[xs.length - 1] + step < union.x1 - 0.3 * step)
|
|
3333
|
+
xs.push(xs[xs.length - 1] + step);
|
|
3334
|
+
xs.push(union.x1);
|
|
3335
|
+
const fixed = /* @__PURE__ */ new Set([union.x0, union.x1]);
|
|
3336
|
+
for (const x of [
|
|
3337
|
+
opening.x0 - half,
|
|
3338
|
+
opening.x1 - half,
|
|
3339
|
+
opening.x1 + 1 - half
|
|
3340
|
+
]) {
|
|
3341
|
+
fixed.add(x);
|
|
3342
|
+
if (xs.includes(x)) continue;
|
|
3343
|
+
const near = xs.findIndex((v) => Math.abs(v - x) < 0.3 * step);
|
|
3344
|
+
if (near >= 0 && !fixed.has(xs[near])) xs[near] = x;
|
|
3345
|
+
else xs.push(x);
|
|
3346
|
+
}
|
|
3347
|
+
xs.sort((p, q) => p - q);
|
|
3348
|
+
for (const x of xs) {
|
|
3349
|
+
if (!Number.isInteger(x + half)) {
|
|
3350
|
+
throw new Error(
|
|
3351
|
+
`auto-rig: mouth knot ${x} is not an integer canvas boundary`
|
|
3352
|
+
);
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3355
|
+
return xs;
|
|
3356
|
+
}
|
|
3357
|
+
function mouthMesh(box, knots) {
|
|
3358
|
+
const xs = [box.x0, ...knots.filter((k) => k > box.x0 && k < box.x1), box.x1];
|
|
3359
|
+
const rows = cellsFor(bh(box), MOUTH_KNOT_PX, 2, 40);
|
|
3360
|
+
const ys = Array.from(
|
|
3361
|
+
{ length: rows + 1 },
|
|
3362
|
+
(_, r) => box.y1 - bh(box) * r / rows
|
|
3363
|
+
);
|
|
3364
|
+
return { mesh: columnMesh(box, { xs, ys }), xs };
|
|
3365
|
+
}
|
|
3366
|
+
function buildMouthRig(byRole) {
|
|
3367
|
+
const opening = mouthOpening(byRole);
|
|
3368
|
+
const union = unionBoxes(LIP_ROLES.map((r) => boxOfLayer(byRole.get(r))));
|
|
3369
|
+
return {
|
|
3370
|
+
opening,
|
|
3371
|
+
frame: mouthFrameOf(union),
|
|
3372
|
+
knots: mouthKnots(union, opening),
|
|
3373
|
+
union
|
|
3374
|
+
};
|
|
3375
|
+
}
|
|
3376
|
+
function mouthAnchor(byRole) {
|
|
3377
|
+
const box = (role) => boxOfLayer(byRole.get(role));
|
|
3378
|
+
if (isLipSet(byRole)) {
|
|
3379
|
+
const opening = mouthOpening(byRole);
|
|
3380
|
+
return {
|
|
3381
|
+
at: {
|
|
3382
|
+
x: cx(box("mouth_inner")),
|
|
3383
|
+
y: opening.at(opening.centre).seam
|
|
3384
|
+
},
|
|
3385
|
+
box: unionBoxes(LIP_ROLES.map(box))
|
|
3386
|
+
};
|
|
3387
|
+
}
|
|
3388
|
+
const m = box("mouth");
|
|
3389
|
+
return {
|
|
3390
|
+
at: { x: cx(m), y: cy(m) },
|
|
3391
|
+
box: byRole.has("mouth_open") ? unionBoxes([m, box("mouth_open")]) : m
|
|
3392
|
+
};
|
|
3393
|
+
}
|
|
3394
|
+
|
|
3101
3395
|
// src/auto-rig/generate.ts
|
|
3102
3396
|
import {
|
|
3103
3397
|
IKI_FORMAT_VERSION as IKI_FORMAT_VERSION2,
|
|
3104
|
-
StandardParameter as
|
|
3398
|
+
StandardParameter as P8,
|
|
3105
3399
|
parseIkiModel as parseIkiModel3
|
|
3106
3400
|
} from "@ikijs/format";
|
|
3107
3401
|
|
|
3108
3402
|
// src/auto-rig/animations.ts
|
|
3109
3403
|
import {
|
|
3110
|
-
StandardParameter as
|
|
3404
|
+
StandardParameter as P6
|
|
3111
3405
|
} from "@ikijs/format";
|
|
3112
3406
|
function browsY(y) {
|
|
3113
3407
|
return [
|
|
3114
|
-
{ parameter:
|
|
3115
|
-
{ parameter:
|
|
3408
|
+
{ parameter: P6.BrowLeftY, value: y, blend: "add" },
|
|
3409
|
+
{ parameter: P6.BrowRightY, value: y, blend: "add" }
|
|
3116
3410
|
];
|
|
3117
3411
|
}
|
|
3118
3412
|
function browsTilt(innerDown) {
|
|
3119
3413
|
return [
|
|
3120
|
-
{ parameter:
|
|
3121
|
-
{ parameter:
|
|
3414
|
+
{ parameter: P6.BrowLeftAngle, value: innerDown, blend: "add" },
|
|
3415
|
+
{ parameter: P6.BrowRightAngle, value: -innerDown, blend: "add" }
|
|
3122
3416
|
];
|
|
3123
3417
|
}
|
|
3124
3418
|
function cheek(value) {
|
|
3125
|
-
return { parameter:
|
|
3419
|
+
return { parameter: P6.Cheek, value, blend: "add" };
|
|
3126
3420
|
}
|
|
3127
|
-
var BROWS = [
|
|
3421
|
+
var BROWS = [P6.BrowLeftY, P6.BrowRightY, P6.BrowLeftAngle, P6.BrowRightAngle];
|
|
3128
3422
|
var EXPRESSIONS = [
|
|
3129
3423
|
{
|
|
3130
3424
|
id: "smile",
|
|
3131
3425
|
description: "Smiling and pleased: warm, friendly, content. For greetings, thanks, agreement and gentle happiness.",
|
|
3132
3426
|
parameters: [
|
|
3133
|
-
{ parameter:
|
|
3427
|
+
{ parameter: P6.MouthForm, value: 1, blend: "add" },
|
|
3134
3428
|
...browsY(0.25),
|
|
3135
3429
|
cheek(0.4)
|
|
3136
3430
|
]
|
|
@@ -3139,10 +3433,10 @@ var EXPRESSIONS = [
|
|
|
3139
3433
|
id: "laugh",
|
|
3140
3434
|
description: "Laughing, eyes shut and mouth open: delighted, amused. For jokes, playfulness and big joy.",
|
|
3141
3435
|
parameters: [
|
|
3142
|
-
{ parameter:
|
|
3143
|
-
{ parameter:
|
|
3144
|
-
{ parameter:
|
|
3145
|
-
{ parameter:
|
|
3436
|
+
{ parameter: P6.EyeOpenLeft, value: 0, blend: "multiply" },
|
|
3437
|
+
{ parameter: P6.EyeOpenRight, value: 0, blend: "multiply" },
|
|
3438
|
+
{ parameter: P6.MouthForm, value: 1, blend: "add" },
|
|
3439
|
+
{ parameter: P6.MouthOpen, value: 0.8, blend: "overwrite" },
|
|
3146
3440
|
...browsY(0.4),
|
|
3147
3441
|
cheek(0.65)
|
|
3148
3442
|
]
|
|
@@ -3154,7 +3448,7 @@ var EXPRESSIONS = [
|
|
|
3154
3448
|
parameters: [
|
|
3155
3449
|
...browsY(-0.5),
|
|
3156
3450
|
...browsTilt(0.8),
|
|
3157
|
-
{ parameter:
|
|
3451
|
+
{ parameter: P6.MouthForm, value: -0.9, blend: "add" }
|
|
3158
3452
|
]
|
|
3159
3453
|
},
|
|
3160
3454
|
{
|
|
@@ -3164,7 +3458,7 @@ var EXPRESSIONS = [
|
|
|
3164
3458
|
parameters: [
|
|
3165
3459
|
...browsY(0.35),
|
|
3166
3460
|
...browsTilt(-0.8),
|
|
3167
|
-
{ parameter:
|
|
3461
|
+
{ parameter: P6.MouthForm, value: -0.8, blend: "add" }
|
|
3168
3462
|
]
|
|
3169
3463
|
},
|
|
3170
3464
|
{
|
|
@@ -3172,18 +3466,18 @@ var EXPRESSIONS = [
|
|
|
3172
3466
|
description: "Surprised, mouth dropped open: startled, amazed. For shock, sudden news or disbelief.",
|
|
3173
3467
|
parameters: [
|
|
3174
3468
|
...browsY(0.8),
|
|
3175
|
-
{ parameter:
|
|
3469
|
+
{ parameter: P6.MouthOpen, value: 0.9, blend: "overwrite" }
|
|
3176
3470
|
]
|
|
3177
3471
|
},
|
|
3178
3472
|
{
|
|
3179
3473
|
id: "shy",
|
|
3180
3474
|
description: "Shy or embarrassed: bashful, flustered. For being praised, teased or caught off guard.",
|
|
3181
|
-
needs: [
|
|
3475
|
+
needs: [P6.Cheek],
|
|
3182
3476
|
parameters: [
|
|
3183
3477
|
cheek(1),
|
|
3184
3478
|
// Gaze down.
|
|
3185
|
-
{ parameter:
|
|
3186
|
-
{ parameter:
|
|
3479
|
+
{ parameter: P6.EyeballY, value: -0.65, blend: "add" },
|
|
3480
|
+
{ parameter: P6.MouthForm, value: 0.4, blend: "add" },
|
|
3187
3481
|
...browsTilt(-0.25)
|
|
3188
3482
|
]
|
|
3189
3483
|
}
|
|
@@ -3203,7 +3497,7 @@ var HEAD_MOTIONS = {
|
|
|
3203
3497
|
duration: 1,
|
|
3204
3498
|
curves: [
|
|
3205
3499
|
{
|
|
3206
|
-
parameter:
|
|
3500
|
+
parameter: P6.AngleY,
|
|
3207
3501
|
keys: [
|
|
3208
3502
|
[0, 0],
|
|
3209
3503
|
[0.3, -18],
|
|
@@ -3224,7 +3518,7 @@ var HEAD_MOTIONS = {
|
|
|
3224
3518
|
fadeIn: 0.15,
|
|
3225
3519
|
curves: [
|
|
3226
3520
|
{
|
|
3227
|
-
parameter:
|
|
3521
|
+
parameter: P6.AngleX,
|
|
3228
3522
|
keys: [
|
|
3229
3523
|
[0, 0],
|
|
3230
3524
|
[0.2, -18],
|
|
@@ -3243,7 +3537,7 @@ var HEAD_MOTIONS = {
|
|
|
3243
3537
|
duration: 1.4,
|
|
3244
3538
|
curves: [
|
|
3245
3539
|
{
|
|
3246
|
-
parameter:
|
|
3540
|
+
parameter: P6.AngleZ,
|
|
3247
3541
|
keys: [
|
|
3248
3542
|
[0, 0],
|
|
3249
3543
|
[0.35, 20],
|
|
@@ -3264,7 +3558,7 @@ var WAVE = {
|
|
|
3264
3558
|
fadeOut: 0.15,
|
|
3265
3559
|
curves: [
|
|
3266
3560
|
{
|
|
3267
|
-
parameter:
|
|
3561
|
+
parameter: P6.ArmPoseRight,
|
|
3268
3562
|
interpolation: "linear",
|
|
3269
3563
|
keys: [
|
|
3270
3564
|
[0, 0],
|
|
@@ -3274,7 +3568,7 @@ var WAVE = {
|
|
|
3274
3568
|
]
|
|
3275
3569
|
},
|
|
3276
3570
|
{
|
|
3277
|
-
parameter:
|
|
3571
|
+
parameter: P6.ArmRight,
|
|
3278
3572
|
keys: [
|
|
3279
3573
|
[0, 0],
|
|
3280
3574
|
[0.4, 10],
|
|
@@ -3283,7 +3577,7 @@ var WAVE = {
|
|
|
3283
3577
|
]
|
|
3284
3578
|
},
|
|
3285
3579
|
{
|
|
3286
|
-
parameter:
|
|
3580
|
+
parameter: P6.ArmPoseAngleRight,
|
|
3287
3581
|
keys: [
|
|
3288
3582
|
[0, 0],
|
|
3289
3583
|
[0.5, 0],
|
|
@@ -3299,7 +3593,7 @@ var WAVE = {
|
|
|
3299
3593
|
]
|
|
3300
3594
|
};
|
|
3301
3595
|
function defaultMotions(declared) {
|
|
3302
|
-
const waves = [
|
|
3596
|
+
const waves = [P6.ArmRight, P6.ArmPoseRight, P6.ArmPoseAngleRight].every(
|
|
3303
3597
|
(id) => declared.has(id)
|
|
3304
3598
|
);
|
|
3305
3599
|
return waves ? { ...HEAD_MOTIONS, ...WAVE } : HEAD_MOTIONS;
|
|
@@ -3408,6 +3702,14 @@ function checkLayers(layers, canvas) {
|
|
|
3408
3702
|
if (missing.length > 0) {
|
|
3409
3703
|
throw new Error(`auto-rig: missing required role(s) ${missing.join(", ")}`);
|
|
3410
3704
|
}
|
|
3705
|
+
checkMouth(seen);
|
|
3706
|
+
for (const l of layers) {
|
|
3707
|
+
if (l.rowRuns === void 0 && isLipRole(l.role)) {
|
|
3708
|
+
throw new Error(
|
|
3709
|
+
`auto-rig: layer "${l.fileName}": the lip set needs rowRuns (the fold reads the opening's rows off them; measure with createLayerSetMeasurer)`
|
|
3710
|
+
);
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3411
3713
|
checkArmsHaveBody(seen);
|
|
3412
3714
|
checkPosesHaveArms(seen);
|
|
3413
3715
|
}
|
|
@@ -3527,7 +3829,7 @@ function checkStrandEdges(strand, irises, hair) {
|
|
|
3527
3829
|
}
|
|
3528
3830
|
|
|
3529
3831
|
// src/auto-rig/context.ts
|
|
3530
|
-
import { StandardParameter as
|
|
3832
|
+
import { StandardParameter as P7 } from "@ikijs/format";
|
|
3531
3833
|
|
|
3532
3834
|
// src/auto-rig/head.ts
|
|
3533
3835
|
function outerEdge(runs, axisX, side) {
|
|
@@ -3607,7 +3909,7 @@ function buildHeadFrame(layers, opts) {
|
|
|
3607
3909
|
return ya === void 0 || yb === void 0 ? void 0 : (ya + yb) / 2;
|
|
3608
3910
|
};
|
|
3609
3911
|
const eyeY = pair("iris_L", "iris_R") ?? pair("eye_L", "eye_R");
|
|
3610
|
-
const mouthY =
|
|
3912
|
+
const mouthY = mouthAnchor(byRole).at.y;
|
|
3611
3913
|
const rowY = (r) => canvasH / 2 - (faceLayer.bbox.y + r + 0.5);
|
|
3612
3914
|
const raw = filledProfile(faceLayer.rowHalfWidths);
|
|
3613
3915
|
const profile = raw === null ? null : smoothProfile(raw);
|
|
@@ -4580,7 +4882,6 @@ function sideIrises(byRole) {
|
|
|
4580
4882
|
return pair === void 0 ? {} : { left: pair.left.box, right: pair.right.box };
|
|
4581
4883
|
}
|
|
4582
4884
|
function solveContext(frame, byRole, options, hasNose) {
|
|
4583
|
-
const box = (role) => boxOfLayer(byRole.get(role));
|
|
4584
4885
|
const row = (s, strand2, sideSign) => {
|
|
4585
4886
|
if (strand2 !== void 0) {
|
|
4586
4887
|
const lo = sideSign < 0 ? strand2.irisOuter : strand2.irisInner;
|
|
@@ -4629,8 +4930,7 @@ function solveContext(frame, byRole, options, hasNose) {
|
|
|
4629
4930
|
noseBox = nose.denseCore ? boxOfPixels(nose.denseCore, nose.canvasW, nose.canvasH) : boxOfLayer(nose);
|
|
4630
4931
|
noseAt = { x: cx(noseBox), y: cy(noseBox) };
|
|
4631
4932
|
}
|
|
4632
|
-
const
|
|
4633
|
-
const m = box("mouth");
|
|
4933
|
+
const mouth = mouthAnchor(byRole);
|
|
4634
4934
|
return {
|
|
4635
4935
|
frame,
|
|
4636
4936
|
left,
|
|
@@ -4642,8 +4942,8 @@ function solveContext(frame, byRole, options, hasNose) {
|
|
|
4642
4942
|
eyeBoxes: { left: eyes.left.box, right: eyes.right.box },
|
|
4643
4943
|
noseAt,
|
|
4644
4944
|
noseBox,
|
|
4645
|
-
mouthAt:
|
|
4646
|
-
mouthBox
|
|
4945
|
+
mouthAt: mouth.at,
|
|
4946
|
+
mouthBox: mouth.box
|
|
4647
4947
|
};
|
|
4648
4948
|
}
|
|
4649
4949
|
function renderedLander(turn, parts, grids, from = /* @__PURE__ */ new Map()) {
|
|
@@ -4659,7 +4959,7 @@ function renderedLander(turn, parts, grids, from = /* @__PURE__ */ new Map()) {
|
|
|
4659
4959
|
const family = roleSpec(role).family;
|
|
4660
4960
|
const part = parts.find((p) => p.id === role);
|
|
4661
4961
|
const g = grids.get(family);
|
|
4662
|
-
const warp = part?.warps?.find((w) => w.parameter ===
|
|
4962
|
+
const warp = part?.warps?.find((w) => w.parameter === P7.AngleX);
|
|
4663
4963
|
if (part === void 0 || part.mesh === void 0 || !g && !warp) {
|
|
4664
4964
|
return x + field(role)(x, y, ax, 0)[0];
|
|
4665
4965
|
}
|
|
@@ -5456,6 +5756,7 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
5456
5756
|
hh: frame.hh,
|
|
5457
5757
|
pivots: [...arms.values()].map((g) => g.shoulder)
|
|
5458
5758
|
}) : void 0;
|
|
5759
|
+
const lips = isLipSet(byRole) ? buildMouthRig(byRole) : void 0;
|
|
5459
5760
|
const parts = [];
|
|
5460
5761
|
const reach = /* @__PURE__ */ new Map();
|
|
5461
5762
|
let regionOf = [];
|
|
@@ -5487,7 +5788,8 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
5487
5788
|
frame,
|
|
5488
5789
|
byRole,
|
|
5489
5790
|
style,
|
|
5490
|
-
landmarks.hairFrontGrid
|
|
5791
|
+
landmarks.hairFrontGrid,
|
|
5792
|
+
lips
|
|
5491
5793
|
);
|
|
5492
5794
|
if (built.region !== void 0) {
|
|
5493
5795
|
regionOf = built.region;
|
|
@@ -5569,14 +5871,14 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
5569
5871
|
function turnWarps(mesh, b, fieldOf) {
|
|
5570
5872
|
return [
|
|
5571
5873
|
localWarp(
|
|
5572
|
-
|
|
5874
|
+
P8.AngleX,
|
|
5573
5875
|
mesh,
|
|
5574
5876
|
b,
|
|
5575
5877
|
X_STOPS,
|
|
5576
5878
|
([x, y], v, i) => v === 0 ? [0, 0] : [fieldOf(i)(x, y, v, 0)[0], 0]
|
|
5577
5879
|
),
|
|
5578
5880
|
localWarp(
|
|
5579
|
-
|
|
5881
|
+
P8.AngleY,
|
|
5580
5882
|
mesh,
|
|
5581
5883
|
b,
|
|
5582
5884
|
Y_STOPS,
|
|
@@ -5589,7 +5891,7 @@ function plateFields(turn, region, head) {
|
|
|
5589
5891
|
const neck = neckField(turn);
|
|
5590
5892
|
return (i) => region[i] === "ear" ? ear : region[i] === "neck" ? neck : head;
|
|
5591
5893
|
}
|
|
5592
|
-
function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
5894
|
+
function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
|
|
5593
5895
|
const box = (role) => boxOfLayer(byRole.get(role));
|
|
5594
5896
|
const part = {
|
|
5595
5897
|
id: spec.role,
|
|
@@ -5604,6 +5906,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5604
5906
|
const bindings = [];
|
|
5605
5907
|
let extra = [0, 0];
|
|
5606
5908
|
let mesh;
|
|
5909
|
+
let knotXs;
|
|
5607
5910
|
let region;
|
|
5608
5911
|
let headStart = 0;
|
|
5609
5912
|
if (spec.role === "face") {
|
|
@@ -5614,6 +5917,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5614
5917
|
headStart = fm.headStart;
|
|
5615
5918
|
} else if (spec.role === "hair_front") {
|
|
5616
5919
|
mesh = columnMesh(b, frontGrid);
|
|
5920
|
+
} else if (lips !== void 0 && isLipRole(spec.role)) {
|
|
5921
|
+
({ mesh, xs: knotXs } = mouthMesh(b, lips.knots));
|
|
5617
5922
|
} else if (spec.family !== "body") {
|
|
5618
5923
|
const cells = MESH_CELLS[spec.role] ?? FEATURE_MESH_CELLS;
|
|
5619
5924
|
const px = Math.min(cells.px, meshScale(spec.role, frame));
|
|
@@ -5624,7 +5929,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5624
5929
|
}
|
|
5625
5930
|
if (mesh !== void 0) part.mesh = mesh;
|
|
5626
5931
|
const side = spec.role.endsWith("_L") ? "L" : "R";
|
|
5627
|
-
const eyeOpen = side === "L" ?
|
|
5932
|
+
const eyeOpen = side === "L" ? P8.EyeOpenLeft : P8.EyeOpenRight;
|
|
5628
5933
|
const hh = frame.hh;
|
|
5629
5934
|
switch (spec.role) {
|
|
5630
5935
|
case "face":
|
|
@@ -5639,7 +5944,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5639
5944
|
headOwnRoll(byRole.has("body"))
|
|
5640
5945
|
),
|
|
5641
5946
|
localWarp(
|
|
5642
|
-
|
|
5947
|
+
P8.Breath,
|
|
5643
5948
|
mesh,
|
|
5644
5949
|
b,
|
|
5645
5950
|
[0, 1],
|
|
@@ -5686,8 +5991,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5686
5991
|
if (byRole.has("mouth_open")) {
|
|
5687
5992
|
warps.push(mouthWiden(mesh, b));
|
|
5688
5993
|
bindings.push(
|
|
5689
|
-
{ parameter:
|
|
5690
|
-
{ parameter:
|
|
5994
|
+
{ parameter: P8.MouthOpen, channel: "opacity", from: 1, to: 0 },
|
|
5995
|
+
{ parameter: P8.MouthOpen, channel: "opacity", from: 1, to: 0 }
|
|
5691
5996
|
);
|
|
5692
5997
|
extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
|
|
5693
5998
|
} else {
|
|
@@ -5697,19 +6002,19 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5697
6002
|
);
|
|
5698
6003
|
bindings.push(
|
|
5699
6004
|
{
|
|
5700
|
-
parameter:
|
|
6005
|
+
parameter: P8.MouthOpen,
|
|
5701
6006
|
channel: "scaleY",
|
|
5702
6007
|
from: 0,
|
|
5703
6008
|
to: roundTo(grow, 0.01)
|
|
5704
6009
|
},
|
|
5705
6010
|
{
|
|
5706
|
-
parameter:
|
|
6011
|
+
parameter: P8.MouthOpen,
|
|
5707
6012
|
channel: "scaleX",
|
|
5708
6013
|
from: 0,
|
|
5709
6014
|
to: roundTo(AMPLITUDE.mouthOpenWidth - 1, 0.01)
|
|
5710
6015
|
},
|
|
5711
6016
|
{
|
|
5712
|
-
parameter:
|
|
6017
|
+
parameter: P8.MouthOpen,
|
|
5713
6018
|
channel: "translateY",
|
|
5714
6019
|
from: 0,
|
|
5715
6020
|
to: -roundTo(grow * bh(b) / 2, 0.01)
|
|
@@ -5721,17 +6026,26 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5721
6026
|
case "mouth_open":
|
|
5722
6027
|
warps.push(mouthForm(mesh, b), mouthOpenGrow(mesh, b));
|
|
5723
6028
|
bindings.push({
|
|
5724
|
-
parameter:
|
|
6029
|
+
parameter: P8.MouthOpen,
|
|
5725
6030
|
channel: "opacity",
|
|
5726
6031
|
from: 0,
|
|
5727
6032
|
to: 1
|
|
5728
6033
|
});
|
|
5729
6034
|
extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
|
|
5730
6035
|
break;
|
|
6036
|
+
case "mouth_inner":
|
|
6037
|
+
case "lip_lower":
|
|
6038
|
+
case "lip_upper":
|
|
6039
|
+
warps.push(
|
|
6040
|
+
mouthFoldWarp(spec.role, mesh, knotXs, b, lips.opening),
|
|
6041
|
+
mouthForm(mesh, b, lips.frame),
|
|
6042
|
+
mouthWiden(mesh, b, lips.frame)
|
|
6043
|
+
);
|
|
6044
|
+
break;
|
|
5731
6045
|
case "blush_L":
|
|
5732
6046
|
case "blush_R":
|
|
5733
6047
|
bindings.push({
|
|
5734
|
-
parameter:
|
|
6048
|
+
parameter: P8.Cheek,
|
|
5735
6049
|
channel: "opacity",
|
|
5736
6050
|
from: AMPLITUDE.blushRest,
|
|
5737
6051
|
to: 1
|
|
@@ -5742,8 +6056,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5742
6056
|
if (byRole.has("hair_front")) {
|
|
5743
6057
|
const amp = style.sway * AMPLITUDE.sway * hh;
|
|
5744
6058
|
warps.push(
|
|
5745
|
-
hairSway(
|
|
5746
|
-
hairSway(
|
|
6059
|
+
hairSway(P8.HairSwayX, mesh, b, amp),
|
|
6060
|
+
hairSway(P8.HairSwayZ, mesh, b, amp)
|
|
5747
6061
|
);
|
|
5748
6062
|
}
|
|
5749
6063
|
warps.push(tiltHang(mesh, b, pivot, hairHangWeight(frame.chinY, b.y0)));
|
|
@@ -5784,29 +6098,6 @@ function hairFrontGrid(b, frame, boxes) {
|
|
|
5784
6098
|
);
|
|
5785
6099
|
return { xs, ys };
|
|
5786
6100
|
}
|
|
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
6101
|
function meshScale(role, frame) {
|
|
5811
6102
|
switch (role) {
|
|
5812
6103
|
case "hair_front":
|
|
@@ -5832,9 +6123,9 @@ function deformers(frame, grids, bodyWarp, arms, byRole) {
|
|
|
5832
6123
|
pivot: { x: roundTo(frame.axisX, 0.01), y: roundTo(frame.chinY, 0.01) },
|
|
5833
6124
|
bindings: [
|
|
5834
6125
|
// AngleZ is clockwise-positive (Live2D's); a rotation is CCW-positive.
|
|
5835
|
-
{ parameter:
|
|
6126
|
+
{ parameter: P8.AngleZ, channel: "rotate", from: roll, to: -roll },
|
|
5836
6127
|
{
|
|
5837
|
-
parameter:
|
|
6128
|
+
parameter: P8.Breath,
|
|
5838
6129
|
channel: "translateY",
|
|
5839
6130
|
from: 0,
|
|
5840
6131
|
to: roundTo(headOwnBreath(hasBody) * frame.hh, 0.01)
|
|
@@ -5852,8 +6143,8 @@ function deformers(frame, grids, bodyWarp, arms, byRole) {
|
|
|
5852
6143
|
points: g.lattice.points
|
|
5853
6144
|
},
|
|
5854
6145
|
warp2d: {
|
|
5855
|
-
parameter:
|
|
5856
|
-
parameterY:
|
|
6146
|
+
parameter: P8.AngleX,
|
|
6147
|
+
parameterY: P8.AngleY,
|
|
5857
6148
|
valuesX: X_STOPS,
|
|
5858
6149
|
valuesY: Y_STOPS,
|
|
5859
6150
|
keyforms2d: g.keyforms.map((offsets) => ({ offsets }))
|
|
@@ -5866,16 +6157,16 @@ function hairPhysics() {
|
|
|
5866
6157
|
return [
|
|
5867
6158
|
{
|
|
5868
6159
|
id: "hairSway",
|
|
5869
|
-
input: { parameter:
|
|
5870
|
-
output: { parameter:
|
|
6160
|
+
input: { parameter: P8.AngleX, weight: 1 },
|
|
6161
|
+
output: { parameter: P8.HairSwayX, scale: 5 },
|
|
5871
6162
|
mass: 1,
|
|
5872
6163
|
stiffness: 30,
|
|
5873
6164
|
damping: 3
|
|
5874
6165
|
},
|
|
5875
6166
|
{
|
|
5876
6167
|
id: "hairTilt",
|
|
5877
|
-
input: { parameter:
|
|
5878
|
-
output: { parameter:
|
|
6168
|
+
input: { parameter: P8.AngleZ, weight: 1 },
|
|
6169
|
+
output: { parameter: P8.HairSwayZ, scale: 8 },
|
|
5879
6170
|
mass: 1,
|
|
5880
6171
|
stiffness: 25,
|
|
5881
6172
|
damping: 2.5
|
|
@@ -5885,13 +6176,13 @@ function hairPhysics() {
|
|
|
5885
6176
|
function declareParameters(roles) {
|
|
5886
6177
|
const out = [];
|
|
5887
6178
|
const add = (id, name, min, max, def) => out.push({ id, name, min, max, default: def });
|
|
5888
|
-
add(
|
|
5889
|
-
add(
|
|
5890
|
-
add(
|
|
6179
|
+
add(P8.AngleX, "Head Angle", -30, 30, 0);
|
|
6180
|
+
add(P8.AngleY, "Head Angle Y", -30, 30, 0);
|
|
6181
|
+
add(P8.AngleZ, "Head Angle Z", -30, 30, 0);
|
|
5891
6182
|
if (roles.has("body")) {
|
|
5892
|
-
add(
|
|
5893
|
-
add(
|
|
5894
|
-
add(
|
|
6183
|
+
add(P8.BodyAngleX, "Body Angle X", -10, 10, 0);
|
|
6184
|
+
add(P8.BodyAngleY, "Body Angle Y", -10, 10, 0);
|
|
6185
|
+
add(P8.BodyAngleZ, "Body Angle Z", -10, 10, 0);
|
|
5895
6186
|
}
|
|
5896
6187
|
const addPose = (side, pose, angle) => {
|
|
5897
6188
|
add(pose, `Arm Pose ${side}`, 0, 1, 0);
|
|
@@ -5900,40 +6191,40 @@ function declareParameters(roles) {
|
|
|
5900
6191
|
const [armMin, armMax] = ARM_RANGE;
|
|
5901
6192
|
const [elbowMin, elbowMax] = ELBOW_RANGE;
|
|
5902
6193
|
if (roles.has("arm_L")) {
|
|
5903
|
-
add(
|
|
5904
|
-
add(
|
|
6194
|
+
add(P8.ArmLeft, "Arm L", armMin, armMax, 0);
|
|
6195
|
+
add(P8.ElbowLeft, "Elbow L", elbowMin, elbowMax, 0);
|
|
5905
6196
|
if (roles.has("forearm_pose_L"))
|
|
5906
|
-
addPose("L",
|
|
6197
|
+
addPose("L", P8.ArmPoseLeft, P8.ArmPoseAngleLeft);
|
|
5907
6198
|
}
|
|
5908
6199
|
if (roles.has("arm_R")) {
|
|
5909
|
-
add(
|
|
5910
|
-
add(
|
|
6200
|
+
add(P8.ArmRight, "Arm R", armMin, armMax, 0);
|
|
6201
|
+
add(P8.ElbowRight, "Elbow R", elbowMin, elbowMax, 0);
|
|
5911
6202
|
if (roles.has("forearm_pose_R"))
|
|
5912
|
-
addPose("R",
|
|
6203
|
+
addPose("R", P8.ArmPoseRight, P8.ArmPoseAngleRight);
|
|
5913
6204
|
}
|
|
5914
|
-
add(
|
|
5915
|
-
add(
|
|
6205
|
+
add(P8.EyeOpenLeft, "Eye L", 0, 1, 1);
|
|
6206
|
+
add(P8.EyeOpenRight, "Eye R", 0, 1, 1);
|
|
5916
6207
|
if ([...roles].some((r) => /^(iris|pupil|highlight)_/.test(r))) {
|
|
5917
|
-
add(
|
|
5918
|
-
add(
|
|
6208
|
+
add(P8.EyeballX, "Gaze X", -1, 1, 0);
|
|
6209
|
+
add(P8.EyeballY, "Gaze Y", -1, 1, 0);
|
|
5919
6210
|
}
|
|
5920
|
-
add(
|
|
5921
|
-
add(
|
|
6211
|
+
add(P8.MouthOpen, "Mouth Open", 0, 1, 0);
|
|
6212
|
+
add(P8.MouthForm, "Mouth Form", -1, 1, 0);
|
|
5922
6213
|
if (roles.has("brow_L")) {
|
|
5923
|
-
add(
|
|
5924
|
-
add(
|
|
6214
|
+
add(P8.BrowLeftY, "Brow L Y", -1, 1, 0);
|
|
6215
|
+
add(P8.BrowLeftAngle, "Brow L Angle", -1, 1, 0);
|
|
5925
6216
|
}
|
|
5926
6217
|
if (roles.has("brow_R")) {
|
|
5927
|
-
add(
|
|
5928
|
-
add(
|
|
6218
|
+
add(P8.BrowRightY, "Brow R Y", -1, 1, 0);
|
|
6219
|
+
add(P8.BrowRightAngle, "Brow R Angle", -1, 1, 0);
|
|
5929
6220
|
}
|
|
5930
6221
|
if (roles.has("blush_L") || roles.has("blush_R")) {
|
|
5931
|
-
add(
|
|
6222
|
+
add(P8.Cheek, "Cheek", 0, 1, 0);
|
|
5932
6223
|
}
|
|
5933
|
-
add(
|
|
6224
|
+
add(P8.Breath, "Breath", 0, 1, 0);
|
|
5934
6225
|
if (roles.has("hair_front")) {
|
|
5935
|
-
add(
|
|
5936
|
-
add(
|
|
6226
|
+
add(P8.HairSwayX, "Hair Sway X", -SWAY_RANGE, SWAY_RANGE, 0);
|
|
6227
|
+
add(P8.HairSwayZ, "Hair Sway Z", -SWAY_RANGE, SWAY_RANGE, 0);
|
|
5937
6228
|
}
|
|
5938
6229
|
return out;
|
|
5939
6230
|
}
|
|
@@ -5951,6 +6242,7 @@ export {
|
|
|
5951
6242
|
DeletePhysicsRig,
|
|
5952
6243
|
EditorDocument,
|
|
5953
6244
|
HEAD_BAND,
|
|
6245
|
+
LIP_ROLES,
|
|
5954
6246
|
LayerGeometryError,
|
|
5955
6247
|
SPECK_CORE_FRACTION,
|
|
5956
6248
|
SetDeformerBindings,
|
|
@@ -5972,6 +6264,7 @@ export {
|
|
|
5972
6264
|
UV_INSET_PX,
|
|
5973
6265
|
armGeometry,
|
|
5974
6266
|
captureBindingEndpoint,
|
|
6267
|
+
columnRuns,
|
|
5975
6268
|
computeGridOffsets,
|
|
5976
6269
|
createDefaultMatrixDeformer,
|
|
5977
6270
|
createDefaultPart,
|
|
@@ -5986,6 +6279,9 @@ export {
|
|
|
5986
6279
|
headHalfOf,
|
|
5987
6280
|
interpolateGridOffsets,
|
|
5988
6281
|
isSpeckCore,
|
|
6282
|
+
mouthFold,
|
|
6283
|
+
mouthOpening,
|
|
6284
|
+
mouthRestShift,
|
|
5989
6285
|
packAtlas,
|
|
5990
6286
|
parseLayerRoles,
|
|
5991
6287
|
partIdsOfRole,
|