@ikijs/editor 0.21.0 → 0.23.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 +181 -4
- package/dist/index.d.ts +181 -4
- package/dist/index.js +355 -202
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +350 -204
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -33,6 +33,8 @@ __export(index_exports, {
|
|
|
33
33
|
DeletePhysicsRig: () => DeletePhysicsRig,
|
|
34
34
|
EditorDocument: () => EditorDocument,
|
|
35
35
|
HEAD_BAND: () => HEAD_BAND,
|
|
36
|
+
LIP_INSIDE_ROLES: () => LIP_INSIDE_ROLES,
|
|
37
|
+
LIP_ROLES: () => LIP_ROLES,
|
|
36
38
|
LayerGeometryError: () => LayerGeometryError,
|
|
37
39
|
SPECK_CORE_FRACTION: () => SPECK_CORE_FRACTION,
|
|
38
40
|
SetDeformerBindings: () => SetDeformerBindings,
|
|
@@ -54,6 +56,7 @@ __export(index_exports, {
|
|
|
54
56
|
UV_INSET_PX: () => UV_INSET_PX,
|
|
55
57
|
armGeometry: () => armGeometry,
|
|
56
58
|
captureBindingEndpoint: () => captureBindingEndpoint,
|
|
59
|
+
columnRuns: () => columnRuns,
|
|
57
60
|
computeGridOffsets: () => computeGridOffsets,
|
|
58
61
|
createDefaultMatrixDeformer: () => createDefaultMatrixDeformer,
|
|
59
62
|
createDefaultPart: () => createDefaultPart,
|
|
@@ -67,7 +70,11 @@ __export(index_exports, {
|
|
|
67
70
|
generateIkiFromLayerSet: () => generateIkiFromLayerSet,
|
|
68
71
|
headHalfOf: () => headHalfOf,
|
|
69
72
|
interpolateGridOffsets: () => interpolateGridOffsets,
|
|
73
|
+
isLipInsideRole: () => isLipInsideRole,
|
|
70
74
|
isSpeckCore: () => isSpeckCore,
|
|
75
|
+
mouthFold: () => mouthFold,
|
|
76
|
+
mouthOpening: () => mouthOpening,
|
|
77
|
+
mouthRestShift: () => mouthRestShift,
|
|
71
78
|
packAtlas: () => packAtlas,
|
|
72
79
|
parseLayerRoles: () => parseLayerRoles,
|
|
73
80
|
partIdsOfRole: () => partIdsOfRole,
|
|
@@ -3087,9 +3094,14 @@ function forearmPoseDeformer(role, arm) {
|
|
|
3087
3094
|
};
|
|
3088
3095
|
}
|
|
3089
3096
|
|
|
3097
|
+
// src/auto-rig/mouth.ts
|
|
3098
|
+
var import_format7 = require("@ikijs/format");
|
|
3099
|
+
|
|
3090
3100
|
// src/auto-rig/roles.ts
|
|
3091
3101
|
var LIP_ROLES = ["mouth_inner", "lip_lower", "lip_upper"];
|
|
3092
3102
|
var isLipRole = (role) => LIP_ROLES.includes(role);
|
|
3103
|
+
var LIP_INSIDE_ROLES = ["mouth_tongue", "mouth_teeth"];
|
|
3104
|
+
var isLipInsideRole = (role) => LIP_INSIDE_ROLES.includes(role);
|
|
3093
3105
|
var ROLE_TABLE = [
|
|
3094
3106
|
{ role: "hair_back", family: "hair_back" },
|
|
3095
3107
|
{ role: "body", family: "body" },
|
|
@@ -3101,10 +3113,13 @@ var ROLE_TABLE = [
|
|
|
3101
3113
|
{ role: "nose", family: "nose" },
|
|
3102
3114
|
{ role: "mouth", family: "mouth" },
|
|
3103
3115
|
{ role: "mouth_open", family: "mouth" },
|
|
3104
|
-
// The lip set, back to front: the interior, the
|
|
3105
|
-
// lip and its line over both. It
|
|
3106
|
-
// (`mouth.ts`), so it stands in for
|
|
3116
|
+
// The lip set, back to front: the interior, the tongue and the teeth clipped
|
|
3117
|
+
// to it, the lower lip's skin, the upper lip and its line over both. It
|
|
3118
|
+
// folds open instead of crossfading (`mouth.ts`), so it stands in for
|
|
3119
|
+
// `mouth`, never beside it.
|
|
3107
3120
|
{ role: "mouth_inner", family: "mouth" },
|
|
3121
|
+
{ role: "mouth_tongue", family: "mouth" },
|
|
3122
|
+
{ role: "mouth_teeth", family: "mouth" },
|
|
3108
3123
|
{ role: "lip_lower", family: "mouth" },
|
|
3109
3124
|
{ role: "lip_upper", family: "mouth" },
|
|
3110
3125
|
{ role: "eye_L", family: "eye_L", required: true },
|
|
@@ -3167,6 +3182,12 @@ function checkArmsHaveBody(roles) {
|
|
|
3167
3182
|
function checkMouth(roles) {
|
|
3168
3183
|
const has = new Set(roles);
|
|
3169
3184
|
const present = LIP_ROLES.filter((r) => has.has(r));
|
|
3185
|
+
const inside = LIP_INSIDE_ROLES.filter((r) => has.has(r));
|
|
3186
|
+
if (inside.length > 0 && present.length === 0) {
|
|
3187
|
+
throw new Error(
|
|
3188
|
+
`auto-rig: ${inside[0]} needs the lip set ${LIP_ROLES.join(", ")} (it is cut from the interior and clipped to it)`
|
|
3189
|
+
);
|
|
3190
|
+
}
|
|
3170
3191
|
if (present.length === 0) {
|
|
3171
3192
|
if (!has.has("mouth")) {
|
|
3172
3193
|
throw new Error(
|
|
@@ -3228,33 +3249,328 @@ function parseLayerRoles(fileNames) {
|
|
|
3228
3249
|
return out;
|
|
3229
3250
|
}
|
|
3230
3251
|
|
|
3252
|
+
// src/auto-rig/mouth.ts
|
|
3253
|
+
var UPPER_SHARE = 0.4;
|
|
3254
|
+
var UPPER_THIN = 0.7;
|
|
3255
|
+
var END_TAPER = 0.35;
|
|
3256
|
+
var END_TAPER_FROM = 0.7;
|
|
3257
|
+
var FLICK_PULL = 0.5;
|
|
3258
|
+
var FLICK_SQUASH = 0.25;
|
|
3259
|
+
var MOUTH_KNOT_PX = 4;
|
|
3260
|
+
var isLipSet = (byRole) => LIP_ROLES.every((r) => byRole.has(r));
|
|
3261
|
+
function columnRuns(layer) {
|
|
3262
|
+
const cols = /* @__PURE__ */ new Map();
|
|
3263
|
+
(layer.rowRuns ?? []).forEach((runs, k) => {
|
|
3264
|
+
for (let i = 0; i < runs.length; i += 2) {
|
|
3265
|
+
for (let c = runs[i]; c < runs[i + 1]; c++) {
|
|
3266
|
+
const list = cols.get(c) ?? [];
|
|
3267
|
+
const last = list[list.length - 1];
|
|
3268
|
+
if (last !== void 0 && last[1] === k) last[1] = k + 1;
|
|
3269
|
+
else list.push([k, k + 1]);
|
|
3270
|
+
cols.set(c, list);
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
});
|
|
3274
|
+
return cols;
|
|
3275
|
+
}
|
|
3276
|
+
var edgesOf = (layer, run) => ({
|
|
3277
|
+
top: layer.canvasH / 2 - (layer.bbox.y + run[0]),
|
|
3278
|
+
bottom: layer.canvasH / 2 - (layer.bbox.y + run[1])
|
|
3279
|
+
});
|
|
3280
|
+
function mouthOpening(byRole) {
|
|
3281
|
+
const inner = byRole.get("mouth_inner");
|
|
3282
|
+
const upper = byRole.get("lip_upper");
|
|
3283
|
+
const lower = byRole.get("lip_lower");
|
|
3284
|
+
const innerCols = columnRuns(inner);
|
|
3285
|
+
if (innerCols.size === 0) {
|
|
3286
|
+
throw new LayerGeometryError(
|
|
3287
|
+
`auto-rig: layer "${inner.fileName}": mouth_inner has no opaque pixel (alpha \u2265 128) to read the opening off`
|
|
3288
|
+
);
|
|
3289
|
+
}
|
|
3290
|
+
const upperCols = columnRuns(upper);
|
|
3291
|
+
const lowerCols = columnRuns(lower);
|
|
3292
|
+
const cols = [...innerCols.keys()];
|
|
3293
|
+
const x0 = Math.min(...cols);
|
|
3294
|
+
const x1 = Math.max(...cols);
|
|
3295
|
+
const read = (col) => {
|
|
3296
|
+
const runs = innerCols.get(col);
|
|
3297
|
+
if (runs === void 0) return void 0;
|
|
3298
|
+
const T = edgesOf(inner, runs[0]).top;
|
|
3299
|
+
const Bb = edgesOf(inner, runs[runs.length - 1]).bottom;
|
|
3300
|
+
const line = [...upperCols.get(col) ?? []].reverse().map((r) => edgesOf(upper, r)).find((e) => e.top >= T);
|
|
3301
|
+
const Tu = line?.bottom ?? T;
|
|
3302
|
+
const lineTop = line?.top ?? T;
|
|
3303
|
+
const skin = lowerCols.get(col)?.[0];
|
|
3304
|
+
return {
|
|
3305
|
+
T,
|
|
3306
|
+
Bb,
|
|
3307
|
+
Tu,
|
|
3308
|
+
lineTop,
|
|
3309
|
+
lineH: lineTop - Tu,
|
|
3310
|
+
Bl: skin === void 0 ? Bb : edgesOf(lower, skin).top,
|
|
3311
|
+
H: T - Bb
|
|
3312
|
+
};
|
|
3313
|
+
};
|
|
3314
|
+
const reads = [];
|
|
3315
|
+
for (let c = x0; c <= x1; c++) {
|
|
3316
|
+
let r;
|
|
3317
|
+
for (let d = 0; (r = read(c - d) ?? read(c + d)) === void 0; d++) ;
|
|
3318
|
+
reads.push(r);
|
|
3319
|
+
}
|
|
3320
|
+
const m = (x0 + x1) / 2;
|
|
3321
|
+
let p = 0;
|
|
3322
|
+
let q = 0;
|
|
3323
|
+
if (reads.length >= 4) {
|
|
3324
|
+
let s11 = 0;
|
|
3325
|
+
let s12 = 0;
|
|
3326
|
+
let s22 = 0;
|
|
3327
|
+
let t1 = 0;
|
|
3328
|
+
let t2 = 0;
|
|
3329
|
+
reads.forEach((r, i) => {
|
|
3330
|
+
const x = x0 + i;
|
|
3331
|
+
const b1 = (x - x0) * (x - x1);
|
|
3332
|
+
const b2 = b1 * (x - m);
|
|
3333
|
+
const d = r.T - UPPER_SHARE * r.H - r.Tu;
|
|
3334
|
+
s11 += b1 * b1;
|
|
3335
|
+
s12 += b1 * b2;
|
|
3336
|
+
s22 += b2 * b2;
|
|
3337
|
+
t1 += b1 * d;
|
|
3338
|
+
t2 += b2 * d;
|
|
3339
|
+
});
|
|
3340
|
+
const det = s11 * s22 - s12 * s12;
|
|
3341
|
+
p = (t1 * s22 - t2 * s12) / det;
|
|
3342
|
+
q = (s11 * t2 - s12 * t1) / det;
|
|
3343
|
+
}
|
|
3344
|
+
const columns = reads.map((r, i) => {
|
|
3345
|
+
const x = x0 + i;
|
|
3346
|
+
return { ...r, seam: r.Tu + (x - x0) * (x - x1) * (p + q * (x - m)) };
|
|
3347
|
+
});
|
|
3348
|
+
const clamp2 = (col) => Math.min(x1, Math.max(x0, col));
|
|
3349
|
+
const base = (col) => columns[clamp2(col) - x0];
|
|
3350
|
+
const centre = Math.round((x0 + x1) / 2);
|
|
3351
|
+
const w = base(centre).lineH;
|
|
3352
|
+
const from = END_TAPER_FROM * ((x1 - x0 + 1) / 2);
|
|
3353
|
+
const squash = (col) => {
|
|
3354
|
+
const d = Math.abs(col - centre);
|
|
3355
|
+
const edge = col < centre ? centre - upper.bbox.x : upper.bbox.x + upper.bbox.w - 1 - centre;
|
|
3356
|
+
const t = d <= from ? 0 : d >= edge ? 1 : (d - from) / (edge - from);
|
|
3357
|
+
return 1 - (1 - END_TAPER) * t;
|
|
3358
|
+
};
|
|
3359
|
+
return {
|
|
3360
|
+
x0,
|
|
3361
|
+
x1,
|
|
3362
|
+
centre,
|
|
3363
|
+
canvasW: inner.canvasW,
|
|
3364
|
+
w,
|
|
3365
|
+
at(col) {
|
|
3366
|
+
const b = base(col);
|
|
3367
|
+
const s = squash(clamp2(col));
|
|
3368
|
+
return {
|
|
3369
|
+
...b,
|
|
3370
|
+
overlap: UPPER_THIN * (b.lineH * (1 - s) / 2 + Math.min(w, b.lineH) * s)
|
|
3371
|
+
};
|
|
3372
|
+
},
|
|
3373
|
+
line(col) {
|
|
3374
|
+
if (col >= x0 && col <= x1) {
|
|
3375
|
+
const b = base(col);
|
|
3376
|
+
return { bottom: b.Tu, top: b.lineTop, squash: squash(col) };
|
|
3377
|
+
}
|
|
3378
|
+
const runs = upperCols.get(col);
|
|
3379
|
+
if (runs === void 0) return void 0;
|
|
3380
|
+
return {
|
|
3381
|
+
bottom: edgesOf(upper, runs[runs.length - 1]).bottom,
|
|
3382
|
+
top: edgesOf(upper, runs[0]).top,
|
|
3383
|
+
squash: FLICK_SQUASH
|
|
3384
|
+
};
|
|
3385
|
+
}
|
|
3386
|
+
};
|
|
3387
|
+
}
|
|
3388
|
+
function mouthFold(role, opening) {
|
|
3389
|
+
const { x0, x1 } = opening;
|
|
3390
|
+
const stackThin = (c, y) => y > c.Tu ? c.Tu + UPPER_THIN * (y - c.Tu) : y;
|
|
3391
|
+
return (col, y, v) => {
|
|
3392
|
+
const k = 1 - v;
|
|
3393
|
+
const inside = col >= x0 && col <= x1;
|
|
3394
|
+
switch (role) {
|
|
3395
|
+
case "lip_lower": {
|
|
3396
|
+
if (!inside) return [0, 0];
|
|
3397
|
+
const c = opening.at(col);
|
|
3398
|
+
return [0, (c.seam + c.overlap - c.Bl) * k];
|
|
3399
|
+
}
|
|
3400
|
+
case "mouth_inner": {
|
|
3401
|
+
const c = opening.at(col);
|
|
3402
|
+
const S = c.seam + c.overlap;
|
|
3403
|
+
return [0, S + v * (stackThin(c, y) - S) - y];
|
|
3404
|
+
}
|
|
3405
|
+
case "mouth_teeth": {
|
|
3406
|
+
const c = opening.at(col);
|
|
3407
|
+
return [0, stackThin(c, y) - y + (c.seam - c.Tu) * k];
|
|
3408
|
+
}
|
|
3409
|
+
case "mouth_tongue": {
|
|
3410
|
+
const c = opening.at(col);
|
|
3411
|
+
return [0, (c.seam + c.overlap - c.Bb) * k];
|
|
3412
|
+
}
|
|
3413
|
+
case "lip_upper": {
|
|
3414
|
+
let c = col;
|
|
3415
|
+
let line = opening.line(c);
|
|
3416
|
+
while (line === void 0)
|
|
3417
|
+
line = opening.line(c += col < x0 ? 1 : -1);
|
|
3418
|
+
const thin = line.bottom + UPPER_THIN * (y - line.bottom);
|
|
3419
|
+
const mid = line.bottom + UPPER_THIN * (line.top - line.bottom) / 2;
|
|
3420
|
+
const travel = inside ? opening.at(col).seam - line.bottom : 0;
|
|
3421
|
+
const shut = mid + line.squash * (thin - mid) + travel;
|
|
3422
|
+
const pull = inside ? 0 : (1 - FLICK_PULL) * ((col < x0 ? x0 : x1 + 1) - col);
|
|
3423
|
+
return [pull * k, thin - y + (shut - thin) * k];
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
};
|
|
3427
|
+
}
|
|
3428
|
+
function columnOfKnot(knot, opening) {
|
|
3429
|
+
const boundary = Math.round(knot + opening.canvasW / 2);
|
|
3430
|
+
return boundary === opening.x1 + 1 ? opening.x1 : boundary;
|
|
3431
|
+
}
|
|
3432
|
+
function mouthFoldWarp(role, mesh, xs, box, opening) {
|
|
3433
|
+
const fold = mouthFold(role, opening);
|
|
3434
|
+
return localWarp(
|
|
3435
|
+
import_format7.StandardParameter.MouthOpen,
|
|
3436
|
+
mesh,
|
|
3437
|
+
box,
|
|
3438
|
+
[0, 1],
|
|
3439
|
+
(p, v, i) => fold(columnOfKnot(xs[i % xs.length], opening), p[1], v)
|
|
3440
|
+
);
|
|
3441
|
+
}
|
|
3442
|
+
function mouthRestShift(byRole) {
|
|
3443
|
+
const rig = buildMouthRig(byRole);
|
|
3444
|
+
const fields = /* @__PURE__ */ new Map();
|
|
3445
|
+
for (const role of ["lip_upper", "lip_lower"]) {
|
|
3446
|
+
const box = boxOfLayer(byRole.get(role));
|
|
3447
|
+
const { mesh, xs } = mouthMesh(box, rig.knots);
|
|
3448
|
+
const warp = mouthFoldWarp(role, mesh, xs, box, rig.opening);
|
|
3449
|
+
const offsets = warp.keyforms[0].offsets;
|
|
3450
|
+
fields.set(role, {
|
|
3451
|
+
box,
|
|
3452
|
+
xs,
|
|
3453
|
+
rows: offsets.length / 2 / xs.length - 1,
|
|
3454
|
+
dy: offsets.filter((_, i) => i % 2 === 1).map((o) => o * bh(box))
|
|
3455
|
+
});
|
|
3456
|
+
}
|
|
3457
|
+
const { canvasW, canvasH } = byRole.get("lip_upper");
|
|
3458
|
+
return (role, col, row) => {
|
|
3459
|
+
const { box, xs, rows, dy } = fields.get(role);
|
|
3460
|
+
const x = col + 0.5 - canvasW / 2;
|
|
3461
|
+
const y = canvasH / 2 - (row + 0.5);
|
|
3462
|
+
if (x < box.x0 || x > box.x1 || y < box.y0 || y > box.y1) return void 0;
|
|
3463
|
+
let k = 1;
|
|
3464
|
+
while (k < xs.length - 1 && xs[k] < x) k++;
|
|
3465
|
+
const down = (box.y1 - y) / bh(box) * rows;
|
|
3466
|
+
const r = Math.min(rows - 1, Math.floor(down));
|
|
3467
|
+
const fx = (x - xs[k - 1]) / (xs[k] - xs[k - 1]);
|
|
3468
|
+
const fy = down - r;
|
|
3469
|
+
const at = (i, j) => dy[i * xs.length + j];
|
|
3470
|
+
const tl = at(r, k - 1);
|
|
3471
|
+
const br = at(r + 1, k);
|
|
3472
|
+
if (fx <= fy) {
|
|
3473
|
+
const bl = at(r + 1, k - 1);
|
|
3474
|
+
return tl + fy * (bl - tl) + fx * (br - bl);
|
|
3475
|
+
}
|
|
3476
|
+
const tr = at(r, k);
|
|
3477
|
+
return tl + fx * (tr - tl) + fy * (br - tr);
|
|
3478
|
+
};
|
|
3479
|
+
}
|
|
3480
|
+
function mouthKnots(union, opening) {
|
|
3481
|
+
const half = opening.canvasW / 2;
|
|
3482
|
+
const step = MOUTH_KNOT_PX;
|
|
3483
|
+
const xs = [union.x0];
|
|
3484
|
+
while (xs[xs.length - 1] + step < union.x1 - 0.3 * step)
|
|
3485
|
+
xs.push(xs[xs.length - 1] + step);
|
|
3486
|
+
xs.push(union.x1);
|
|
3487
|
+
const fixed = /* @__PURE__ */ new Set([union.x0, union.x1]);
|
|
3488
|
+
for (const x of [
|
|
3489
|
+
opening.x0 - half,
|
|
3490
|
+
opening.x1 - half,
|
|
3491
|
+
opening.x1 + 1 - half
|
|
3492
|
+
]) {
|
|
3493
|
+
fixed.add(x);
|
|
3494
|
+
if (xs.includes(x)) continue;
|
|
3495
|
+
const near = xs.findIndex((v) => Math.abs(v - x) < 0.3 * step);
|
|
3496
|
+
if (near >= 0 && !fixed.has(xs[near])) xs[near] = x;
|
|
3497
|
+
else xs.push(x);
|
|
3498
|
+
}
|
|
3499
|
+
xs.sort((p, q) => p - q);
|
|
3500
|
+
for (const x of xs) {
|
|
3501
|
+
if (!Number.isInteger(x + half)) {
|
|
3502
|
+
throw new Error(
|
|
3503
|
+
`auto-rig: mouth knot ${x} is not an integer canvas boundary`
|
|
3504
|
+
);
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
return xs;
|
|
3508
|
+
}
|
|
3509
|
+
function mouthMesh(box, knots) {
|
|
3510
|
+
const xs = [box.x0, ...knots.filter((k) => k > box.x0 && k < box.x1), box.x1];
|
|
3511
|
+
const rows = cellsFor(bh(box), MOUTH_KNOT_PX, 2, 40);
|
|
3512
|
+
const ys = Array.from(
|
|
3513
|
+
{ length: rows + 1 },
|
|
3514
|
+
(_, r) => box.y1 - bh(box) * r / rows
|
|
3515
|
+
);
|
|
3516
|
+
return { mesh: columnMesh(box, { xs, ys }), xs };
|
|
3517
|
+
}
|
|
3518
|
+
function buildMouthRig(byRole) {
|
|
3519
|
+
const opening = mouthOpening(byRole);
|
|
3520
|
+
const union = unionBoxes(LIP_ROLES.map((r) => boxOfLayer(byRole.get(r))));
|
|
3521
|
+
return {
|
|
3522
|
+
opening,
|
|
3523
|
+
frame: mouthFrameOf(union),
|
|
3524
|
+
knots: mouthKnots(union, opening),
|
|
3525
|
+
union
|
|
3526
|
+
};
|
|
3527
|
+
}
|
|
3528
|
+
function mouthAnchor(byRole) {
|
|
3529
|
+
const box = (role) => boxOfLayer(byRole.get(role));
|
|
3530
|
+
if (isLipSet(byRole)) {
|
|
3531
|
+
const opening = mouthOpening(byRole);
|
|
3532
|
+
return {
|
|
3533
|
+
at: {
|
|
3534
|
+
x: cx(box("mouth_inner")),
|
|
3535
|
+
y: opening.at(opening.centre).seam
|
|
3536
|
+
},
|
|
3537
|
+
box: unionBoxes(LIP_ROLES.map(box))
|
|
3538
|
+
};
|
|
3539
|
+
}
|
|
3540
|
+
const m = box("mouth");
|
|
3541
|
+
return {
|
|
3542
|
+
at: { x: cx(m), y: cy(m) },
|
|
3543
|
+
box: byRole.has("mouth_open") ? unionBoxes([m, box("mouth_open")]) : m
|
|
3544
|
+
};
|
|
3545
|
+
}
|
|
3546
|
+
|
|
3231
3547
|
// src/auto-rig/generate.ts
|
|
3232
3548
|
var import_format10 = require("@ikijs/format");
|
|
3233
3549
|
|
|
3234
3550
|
// src/auto-rig/animations.ts
|
|
3235
|
-
var
|
|
3551
|
+
var import_format8 = require("@ikijs/format");
|
|
3236
3552
|
function browsY(y) {
|
|
3237
3553
|
return [
|
|
3238
|
-
{ parameter:
|
|
3239
|
-
{ parameter:
|
|
3554
|
+
{ parameter: import_format8.StandardParameter.BrowLeftY, value: y, blend: "add" },
|
|
3555
|
+
{ parameter: import_format8.StandardParameter.BrowRightY, value: y, blend: "add" }
|
|
3240
3556
|
];
|
|
3241
3557
|
}
|
|
3242
3558
|
function browsTilt(innerDown) {
|
|
3243
3559
|
return [
|
|
3244
|
-
{ parameter:
|
|
3245
|
-
{ parameter:
|
|
3560
|
+
{ parameter: import_format8.StandardParameter.BrowLeftAngle, value: innerDown, blend: "add" },
|
|
3561
|
+
{ parameter: import_format8.StandardParameter.BrowRightAngle, value: -innerDown, blend: "add" }
|
|
3246
3562
|
];
|
|
3247
3563
|
}
|
|
3248
3564
|
function cheek(value) {
|
|
3249
|
-
return { parameter:
|
|
3565
|
+
return { parameter: import_format8.StandardParameter.Cheek, value, blend: "add" };
|
|
3250
3566
|
}
|
|
3251
|
-
var BROWS = [
|
|
3567
|
+
var BROWS = [import_format8.StandardParameter.BrowLeftY, import_format8.StandardParameter.BrowRightY, import_format8.StandardParameter.BrowLeftAngle, import_format8.StandardParameter.BrowRightAngle];
|
|
3252
3568
|
var EXPRESSIONS = [
|
|
3253
3569
|
{
|
|
3254
3570
|
id: "smile",
|
|
3255
3571
|
description: "Smiling and pleased: warm, friendly, content. For greetings, thanks, agreement and gentle happiness.",
|
|
3256
3572
|
parameters: [
|
|
3257
|
-
{ parameter:
|
|
3573
|
+
{ parameter: import_format8.StandardParameter.MouthForm, value: 1, blend: "add" },
|
|
3258
3574
|
...browsY(0.25),
|
|
3259
3575
|
cheek(0.4)
|
|
3260
3576
|
]
|
|
@@ -3263,10 +3579,10 @@ var EXPRESSIONS = [
|
|
|
3263
3579
|
id: "laugh",
|
|
3264
3580
|
description: "Laughing, eyes shut and mouth open: delighted, amused. For jokes, playfulness and big joy.",
|
|
3265
3581
|
parameters: [
|
|
3266
|
-
{ parameter:
|
|
3267
|
-
{ parameter:
|
|
3268
|
-
{ parameter:
|
|
3269
|
-
{ parameter:
|
|
3582
|
+
{ parameter: import_format8.StandardParameter.EyeOpenLeft, value: 0, blend: "multiply" },
|
|
3583
|
+
{ parameter: import_format8.StandardParameter.EyeOpenRight, value: 0, blend: "multiply" },
|
|
3584
|
+
{ parameter: import_format8.StandardParameter.MouthForm, value: 1, blend: "add" },
|
|
3585
|
+
{ parameter: import_format8.StandardParameter.MouthOpen, value: 0.8, blend: "overwrite" },
|
|
3270
3586
|
...browsY(0.4),
|
|
3271
3587
|
cheek(0.65)
|
|
3272
3588
|
]
|
|
@@ -3278,7 +3594,7 @@ var EXPRESSIONS = [
|
|
|
3278
3594
|
parameters: [
|
|
3279
3595
|
...browsY(-0.5),
|
|
3280
3596
|
...browsTilt(0.8),
|
|
3281
|
-
{ parameter:
|
|
3597
|
+
{ parameter: import_format8.StandardParameter.MouthForm, value: -0.9, blend: "add" }
|
|
3282
3598
|
]
|
|
3283
3599
|
},
|
|
3284
3600
|
{
|
|
@@ -3288,7 +3604,7 @@ var EXPRESSIONS = [
|
|
|
3288
3604
|
parameters: [
|
|
3289
3605
|
...browsY(0.35),
|
|
3290
3606
|
...browsTilt(-0.8),
|
|
3291
|
-
{ parameter:
|
|
3607
|
+
{ parameter: import_format8.StandardParameter.MouthForm, value: -0.8, blend: "add" }
|
|
3292
3608
|
]
|
|
3293
3609
|
},
|
|
3294
3610
|
{
|
|
@@ -3296,18 +3612,18 @@ var EXPRESSIONS = [
|
|
|
3296
3612
|
description: "Surprised, mouth dropped open: startled, amazed. For shock, sudden news or disbelief.",
|
|
3297
3613
|
parameters: [
|
|
3298
3614
|
...browsY(0.8),
|
|
3299
|
-
{ parameter:
|
|
3615
|
+
{ parameter: import_format8.StandardParameter.MouthOpen, value: 0.9, blend: "overwrite" }
|
|
3300
3616
|
]
|
|
3301
3617
|
},
|
|
3302
3618
|
{
|
|
3303
3619
|
id: "shy",
|
|
3304
3620
|
description: "Shy or embarrassed: bashful, flustered. For being praised, teased or caught off guard.",
|
|
3305
|
-
needs: [
|
|
3621
|
+
needs: [import_format8.StandardParameter.Cheek],
|
|
3306
3622
|
parameters: [
|
|
3307
3623
|
cheek(1),
|
|
3308
3624
|
// Gaze down.
|
|
3309
|
-
{ parameter:
|
|
3310
|
-
{ parameter:
|
|
3625
|
+
{ parameter: import_format8.StandardParameter.EyeballY, value: -0.65, blend: "add" },
|
|
3626
|
+
{ parameter: import_format8.StandardParameter.MouthForm, value: 0.4, blend: "add" },
|
|
3311
3627
|
...browsTilt(-0.25)
|
|
3312
3628
|
]
|
|
3313
3629
|
}
|
|
@@ -3327,7 +3643,7 @@ var HEAD_MOTIONS = {
|
|
|
3327
3643
|
duration: 1,
|
|
3328
3644
|
curves: [
|
|
3329
3645
|
{
|
|
3330
|
-
parameter:
|
|
3646
|
+
parameter: import_format8.StandardParameter.AngleY,
|
|
3331
3647
|
keys: [
|
|
3332
3648
|
[0, 0],
|
|
3333
3649
|
[0.3, -18],
|
|
@@ -3348,7 +3664,7 @@ var HEAD_MOTIONS = {
|
|
|
3348
3664
|
fadeIn: 0.15,
|
|
3349
3665
|
curves: [
|
|
3350
3666
|
{
|
|
3351
|
-
parameter:
|
|
3667
|
+
parameter: import_format8.StandardParameter.AngleX,
|
|
3352
3668
|
keys: [
|
|
3353
3669
|
[0, 0],
|
|
3354
3670
|
[0.2, -18],
|
|
@@ -3367,7 +3683,7 @@ var HEAD_MOTIONS = {
|
|
|
3367
3683
|
duration: 1.4,
|
|
3368
3684
|
curves: [
|
|
3369
3685
|
{
|
|
3370
|
-
parameter:
|
|
3686
|
+
parameter: import_format8.StandardParameter.AngleZ,
|
|
3371
3687
|
keys: [
|
|
3372
3688
|
[0, 0],
|
|
3373
3689
|
[0.35, 20],
|
|
@@ -3388,7 +3704,7 @@ var WAVE = {
|
|
|
3388
3704
|
fadeOut: 0.15,
|
|
3389
3705
|
curves: [
|
|
3390
3706
|
{
|
|
3391
|
-
parameter:
|
|
3707
|
+
parameter: import_format8.StandardParameter.ArmPoseRight,
|
|
3392
3708
|
interpolation: "linear",
|
|
3393
3709
|
keys: [
|
|
3394
3710
|
[0, 0],
|
|
@@ -3398,7 +3714,7 @@ var WAVE = {
|
|
|
3398
3714
|
]
|
|
3399
3715
|
},
|
|
3400
3716
|
{
|
|
3401
|
-
parameter:
|
|
3717
|
+
parameter: import_format8.StandardParameter.ArmRight,
|
|
3402
3718
|
keys: [
|
|
3403
3719
|
[0, 0],
|
|
3404
3720
|
[0.4, 10],
|
|
@@ -3407,7 +3723,7 @@ var WAVE = {
|
|
|
3407
3723
|
]
|
|
3408
3724
|
},
|
|
3409
3725
|
{
|
|
3410
|
-
parameter:
|
|
3726
|
+
parameter: import_format8.StandardParameter.ArmPoseAngleRight,
|
|
3411
3727
|
keys: [
|
|
3412
3728
|
[0, 0],
|
|
3413
3729
|
[0.5, 0],
|
|
@@ -3423,7 +3739,7 @@ var WAVE = {
|
|
|
3423
3739
|
]
|
|
3424
3740
|
};
|
|
3425
3741
|
function defaultMotions(declared) {
|
|
3426
|
-
const waves = [
|
|
3742
|
+
const waves = [import_format8.StandardParameter.ArmRight, import_format8.StandardParameter.ArmPoseRight, import_format8.StandardParameter.ArmPoseAngleRight].every(
|
|
3427
3743
|
(id) => declared.has(id)
|
|
3428
3744
|
);
|
|
3429
3745
|
return waves ? { ...HEAD_MOTIONS, ...WAVE } : HEAD_MOTIONS;
|
|
@@ -3661,179 +3977,6 @@ function checkStrandEdges(strand, irises, hair) {
|
|
|
3661
3977
|
// src/auto-rig/context.ts
|
|
3662
3978
|
var import_format9 = require("@ikijs/format");
|
|
3663
3979
|
|
|
3664
|
-
// src/auto-rig/mouth.ts
|
|
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
|
-
}
|
|
3836
|
-
|
|
3837
3980
|
// src/auto-rig/head.ts
|
|
3838
3981
|
function outerEdge(runs, axisX, side) {
|
|
3839
3982
|
if (runs.length === 0) return void 0;
|
|
@@ -5920,7 +6063,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
|
|
|
5920
6063
|
headStart = fm.headStart;
|
|
5921
6064
|
} else if (spec.role === "hair_front") {
|
|
5922
6065
|
mesh = columnMesh(b, frontGrid);
|
|
5923
|
-
} else if (lips !== void 0 && isLipRole(spec.role)) {
|
|
6066
|
+
} else if (lips !== void 0 && (isLipRole(spec.role) || isLipInsideRole(spec.role))) {
|
|
5924
6067
|
({ mesh, xs: knotXs } = mouthMesh(b, lips.knots));
|
|
5925
6068
|
} else if (spec.family !== "body") {
|
|
5926
6069
|
const cells = MESH_CELLS[spec.role] ?? FEATURE_MESH_CELLS;
|
|
@@ -6037,6 +6180,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
|
|
|
6037
6180
|
extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
|
|
6038
6181
|
break;
|
|
6039
6182
|
case "mouth_inner":
|
|
6183
|
+
case "mouth_tongue":
|
|
6184
|
+
case "mouth_teeth":
|
|
6040
6185
|
case "lip_lower":
|
|
6041
6186
|
case "lip_upper":
|
|
6042
6187
|
warps.push(
|
|
@@ -6044,6 +6189,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
|
|
|
6044
6189
|
mouthForm(mesh, b, lips.frame),
|
|
6045
6190
|
mouthWiden(mesh, b, lips.frame)
|
|
6046
6191
|
);
|
|
6192
|
+
if (isLipInsideRole(spec.role)) part.clip = { masks: ["mouth_inner"] };
|
|
6047
6193
|
break;
|
|
6048
6194
|
case "blush_L":
|
|
6049
6195
|
case "blush_R":
|
|
@@ -6246,6 +6392,8 @@ function declareParameters(roles) {
|
|
|
6246
6392
|
DeletePhysicsRig,
|
|
6247
6393
|
EditorDocument,
|
|
6248
6394
|
HEAD_BAND,
|
|
6395
|
+
LIP_INSIDE_ROLES,
|
|
6396
|
+
LIP_ROLES,
|
|
6249
6397
|
LayerGeometryError,
|
|
6250
6398
|
SPECK_CORE_FRACTION,
|
|
6251
6399
|
SetDeformerBindings,
|
|
@@ -6267,6 +6415,7 @@ function declareParameters(roles) {
|
|
|
6267
6415
|
UV_INSET_PX,
|
|
6268
6416
|
armGeometry,
|
|
6269
6417
|
captureBindingEndpoint,
|
|
6418
|
+
columnRuns,
|
|
6270
6419
|
computeGridOffsets,
|
|
6271
6420
|
createDefaultMatrixDeformer,
|
|
6272
6421
|
createDefaultPart,
|
|
@@ -6280,7 +6429,11 @@ function declareParameters(roles) {
|
|
|
6280
6429
|
generateIkiFromLayerSet,
|
|
6281
6430
|
headHalfOf,
|
|
6282
6431
|
interpolateGridOffsets,
|
|
6432
|
+
isLipInsideRole,
|
|
6283
6433
|
isSpeckCore,
|
|
6434
|
+
mouthFold,
|
|
6435
|
+
mouthOpening,
|
|
6436
|
+
mouthRestShift,
|
|
6284
6437
|
packAtlas,
|
|
6285
6438
|
parseLayerRoles,
|
|
6286
6439
|
partIdsOfRole,
|