@ikijs/editor 0.22.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 +1 -1
- package/dist/index.d.mts +91 -35
- package/dist/index.d.ts +91 -35
- package/dist/index.js +117 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +115 -32
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ __export(index_exports, {
|
|
|
33
33
|
DeletePhysicsRig: () => DeletePhysicsRig,
|
|
34
34
|
EditorDocument: () => EditorDocument,
|
|
35
35
|
HEAD_BAND: () => HEAD_BAND,
|
|
36
|
+
LIP_INSIDE_ROLES: () => LIP_INSIDE_ROLES,
|
|
36
37
|
LIP_ROLES: () => LIP_ROLES,
|
|
37
38
|
LayerGeometryError: () => LayerGeometryError,
|
|
38
39
|
SPECK_CORE_FRACTION: () => SPECK_CORE_FRACTION,
|
|
@@ -69,6 +70,7 @@ __export(index_exports, {
|
|
|
69
70
|
generateIkiFromLayerSet: () => generateIkiFromLayerSet,
|
|
70
71
|
headHalfOf: () => headHalfOf,
|
|
71
72
|
interpolateGridOffsets: () => interpolateGridOffsets,
|
|
73
|
+
isLipInsideRole: () => isLipInsideRole,
|
|
72
74
|
isSpeckCore: () => isSpeckCore,
|
|
73
75
|
mouthFold: () => mouthFold,
|
|
74
76
|
mouthOpening: () => mouthOpening,
|
|
@@ -3098,6 +3100,8 @@ var import_format7 = require("@ikijs/format");
|
|
|
3098
3100
|
// src/auto-rig/roles.ts
|
|
3099
3101
|
var LIP_ROLES = ["mouth_inner", "lip_lower", "lip_upper"];
|
|
3100
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);
|
|
3101
3105
|
var ROLE_TABLE = [
|
|
3102
3106
|
{ role: "hair_back", family: "hair_back" },
|
|
3103
3107
|
{ role: "body", family: "body" },
|
|
@@ -3109,10 +3113,13 @@ var ROLE_TABLE = [
|
|
|
3109
3113
|
{ role: "nose", family: "nose" },
|
|
3110
3114
|
{ role: "mouth", family: "mouth" },
|
|
3111
3115
|
{ role: "mouth_open", family: "mouth" },
|
|
3112
|
-
// The lip set, back to front: the interior, the
|
|
3113
|
-
// lip and its line over both. It
|
|
3114
|
-
// (`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.
|
|
3115
3120
|
{ role: "mouth_inner", family: "mouth" },
|
|
3121
|
+
{ role: "mouth_tongue", family: "mouth" },
|
|
3122
|
+
{ role: "mouth_teeth", family: "mouth" },
|
|
3116
3123
|
{ role: "lip_lower", family: "mouth" },
|
|
3117
3124
|
{ role: "lip_upper", family: "mouth" },
|
|
3118
3125
|
{ role: "eye_L", family: "eye_L", required: true },
|
|
@@ -3175,6 +3182,12 @@ function checkArmsHaveBody(roles) {
|
|
|
3175
3182
|
function checkMouth(roles) {
|
|
3176
3183
|
const has = new Set(roles);
|
|
3177
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
|
+
}
|
|
3178
3191
|
if (present.length === 0) {
|
|
3179
3192
|
if (!has.has("mouth")) {
|
|
3180
3193
|
throw new Error(
|
|
@@ -3238,6 +3251,11 @@ function parseLayerRoles(fileNames) {
|
|
|
3238
3251
|
|
|
3239
3252
|
// src/auto-rig/mouth.ts
|
|
3240
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;
|
|
3241
3259
|
var MOUTH_KNOT_PX = 4;
|
|
3242
3260
|
var isLipSet = (byRole) => LIP_ROLES.every((r) => byRole.has(r));
|
|
3243
3261
|
function columnRuns(layer) {
|
|
@@ -3327,9 +3345,17 @@ function mouthOpening(byRole) {
|
|
|
3327
3345
|
const x = x0 + i;
|
|
3328
3346
|
return { ...r, seam: r.Tu + (x - x0) * (x - x1) * (p + q * (x - m)) };
|
|
3329
3347
|
});
|
|
3330
|
-
const
|
|
3348
|
+
const clamp2 = (col) => Math.min(x1, Math.max(x0, col));
|
|
3349
|
+
const base = (col) => columns[clamp2(col) - x0];
|
|
3331
3350
|
const centre = Math.round((x0 + x1) / 2);
|
|
3332
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
|
+
};
|
|
3333
3359
|
return {
|
|
3334
3360
|
x0,
|
|
3335
3361
|
x1,
|
|
@@ -3338,39 +3364,80 @@ function mouthOpening(byRole) {
|
|
|
3338
3364
|
w,
|
|
3339
3365
|
at(col) {
|
|
3340
3366
|
const b = base(col);
|
|
3341
|
-
|
|
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
|
+
};
|
|
3342
3385
|
}
|
|
3343
3386
|
};
|
|
3344
3387
|
}
|
|
3345
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;
|
|
3346
3391
|
return (col, y, v) => {
|
|
3347
|
-
if (v === 1) return 0;
|
|
3348
|
-
if (role !== "mouth_inner" && (col < opening.x0 || col > opening.x1))
|
|
3349
|
-
return 0;
|
|
3350
|
-
const c = opening.at(col);
|
|
3351
3392
|
const k = 1 - v;
|
|
3393
|
+
const inside = col >= x0 && col <= x1;
|
|
3352
3394
|
switch (role) {
|
|
3353
|
-
case "
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
return (c.seam + c.overlap - c.Bl) * k;
|
|
3357
|
-
|
|
3358
|
-
|
|
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
|
+
}
|
|
3359
3425
|
}
|
|
3360
3426
|
};
|
|
3361
3427
|
}
|
|
3362
|
-
function columnOfKnot(knot, opening
|
|
3428
|
+
function columnOfKnot(knot, opening) {
|
|
3363
3429
|
const boundary = Math.round(knot + opening.canvasW / 2);
|
|
3364
|
-
|
|
3365
|
-
if (col >= opening.x0 && col <= opening.x1) return col;
|
|
3366
|
-
return role === "mouth_inner" ? Math.min(opening.x1, Math.max(opening.x0, col)) : void 0;
|
|
3430
|
+
return boundary === opening.x1 + 1 ? opening.x1 : boundary;
|
|
3367
3431
|
}
|
|
3368
3432
|
function mouthFoldWarp(role, mesh, xs, box, opening) {
|
|
3369
3433
|
const fold = mouthFold(role, opening);
|
|
3370
|
-
return localWarp(
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
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
|
+
);
|
|
3374
3441
|
}
|
|
3375
3442
|
function mouthRestShift(byRole) {
|
|
3376
3443
|
const rig = buildMouthRig(byRole);
|
|
@@ -3383,18 +3450,31 @@ function mouthRestShift(byRole) {
|
|
|
3383
3450
|
fields.set(role, {
|
|
3384
3451
|
box,
|
|
3385
3452
|
xs,
|
|
3386
|
-
|
|
3453
|
+
rows: offsets.length / 2 / xs.length - 1,
|
|
3454
|
+
dy: offsets.filter((_, i) => i % 2 === 1).map((o) => o * bh(box))
|
|
3387
3455
|
});
|
|
3388
3456
|
}
|
|
3389
|
-
const
|
|
3390
|
-
return (role, col) => {
|
|
3391
|
-
const { box, xs, dy } = fields.get(role);
|
|
3392
|
-
const x = col + 0.5 -
|
|
3393
|
-
|
|
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;
|
|
3394
3463
|
let k = 1;
|
|
3395
3464
|
while (k < xs.length - 1 && xs[k] < x) k++;
|
|
3396
|
-
const
|
|
3397
|
-
|
|
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);
|
|
3398
3478
|
};
|
|
3399
3479
|
}
|
|
3400
3480
|
function mouthKnots(union, opening) {
|
|
@@ -5983,7 +6063,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
|
|
|
5983
6063
|
headStart = fm.headStart;
|
|
5984
6064
|
} else if (spec.role === "hair_front") {
|
|
5985
6065
|
mesh = columnMesh(b, frontGrid);
|
|
5986
|
-
} else if (lips !== void 0 && isLipRole(spec.role)) {
|
|
6066
|
+
} else if (lips !== void 0 && (isLipRole(spec.role) || isLipInsideRole(spec.role))) {
|
|
5987
6067
|
({ mesh, xs: knotXs } = mouthMesh(b, lips.knots));
|
|
5988
6068
|
} else if (spec.family !== "body") {
|
|
5989
6069
|
const cells = MESH_CELLS[spec.role] ?? FEATURE_MESH_CELLS;
|
|
@@ -6100,6 +6180,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
|
|
|
6100
6180
|
extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
|
|
6101
6181
|
break;
|
|
6102
6182
|
case "mouth_inner":
|
|
6183
|
+
case "mouth_tongue":
|
|
6184
|
+
case "mouth_teeth":
|
|
6103
6185
|
case "lip_lower":
|
|
6104
6186
|
case "lip_upper":
|
|
6105
6187
|
warps.push(
|
|
@@ -6107,6 +6189,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
|
|
|
6107
6189
|
mouthForm(mesh, b, lips.frame),
|
|
6108
6190
|
mouthWiden(mesh, b, lips.frame)
|
|
6109
6191
|
);
|
|
6192
|
+
if (isLipInsideRole(spec.role)) part.clip = { masks: ["mouth_inner"] };
|
|
6110
6193
|
break;
|
|
6111
6194
|
case "blush_L":
|
|
6112
6195
|
case "blush_R":
|
|
@@ -6309,6 +6392,7 @@ function declareParameters(roles) {
|
|
|
6309
6392
|
DeletePhysicsRig,
|
|
6310
6393
|
EditorDocument,
|
|
6311
6394
|
HEAD_BAND,
|
|
6395
|
+
LIP_INSIDE_ROLES,
|
|
6312
6396
|
LIP_ROLES,
|
|
6313
6397
|
LayerGeometryError,
|
|
6314
6398
|
SPECK_CORE_FRACTION,
|
|
@@ -6345,6 +6429,7 @@ function declareParameters(roles) {
|
|
|
6345
6429
|
generateIkiFromLayerSet,
|
|
6346
6430
|
headHalfOf,
|
|
6347
6431
|
interpolateGridOffsets,
|
|
6432
|
+
isLipInsideRole,
|
|
6348
6433
|
isSpeckCore,
|
|
6349
6434
|
mouthFold,
|
|
6350
6435
|
mouthOpening,
|