@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.mjs
CHANGED
|
@@ -3026,6 +3026,8 @@ import {
|
|
|
3026
3026
|
// src/auto-rig/roles.ts
|
|
3027
3027
|
var LIP_ROLES = ["mouth_inner", "lip_lower", "lip_upper"];
|
|
3028
3028
|
var isLipRole = (role) => LIP_ROLES.includes(role);
|
|
3029
|
+
var LIP_INSIDE_ROLES = ["mouth_tongue", "mouth_teeth"];
|
|
3030
|
+
var isLipInsideRole = (role) => LIP_INSIDE_ROLES.includes(role);
|
|
3029
3031
|
var ROLE_TABLE = [
|
|
3030
3032
|
{ role: "hair_back", family: "hair_back" },
|
|
3031
3033
|
{ role: "body", family: "body" },
|
|
@@ -3037,10 +3039,13 @@ var ROLE_TABLE = [
|
|
|
3037
3039
|
{ role: "nose", family: "nose" },
|
|
3038
3040
|
{ role: "mouth", family: "mouth" },
|
|
3039
3041
|
{ role: "mouth_open", family: "mouth" },
|
|
3040
|
-
// The lip set, back to front: the interior, the
|
|
3041
|
-
// lip and its line over both. It
|
|
3042
|
-
// (`mouth.ts`), so it stands in for
|
|
3042
|
+
// The lip set, back to front: the interior, the tongue and the teeth clipped
|
|
3043
|
+
// to it, the lower lip's skin, the upper lip and its line over both. It
|
|
3044
|
+
// folds open instead of crossfading (`mouth.ts`), so it stands in for
|
|
3045
|
+
// `mouth`, never beside it.
|
|
3043
3046
|
{ role: "mouth_inner", family: "mouth" },
|
|
3047
|
+
{ role: "mouth_tongue", family: "mouth" },
|
|
3048
|
+
{ role: "mouth_teeth", family: "mouth" },
|
|
3044
3049
|
{ role: "lip_lower", family: "mouth" },
|
|
3045
3050
|
{ role: "lip_upper", family: "mouth" },
|
|
3046
3051
|
{ role: "eye_L", family: "eye_L", required: true },
|
|
@@ -3103,6 +3108,12 @@ function checkArmsHaveBody(roles) {
|
|
|
3103
3108
|
function checkMouth(roles) {
|
|
3104
3109
|
const has = new Set(roles);
|
|
3105
3110
|
const present = LIP_ROLES.filter((r) => has.has(r));
|
|
3111
|
+
const inside = LIP_INSIDE_ROLES.filter((r) => has.has(r));
|
|
3112
|
+
if (inside.length > 0 && present.length === 0) {
|
|
3113
|
+
throw new Error(
|
|
3114
|
+
`auto-rig: ${inside[0]} needs the lip set ${LIP_ROLES.join(", ")} (it is cut from the interior and clipped to it)`
|
|
3115
|
+
);
|
|
3116
|
+
}
|
|
3106
3117
|
if (present.length === 0) {
|
|
3107
3118
|
if (!has.has("mouth")) {
|
|
3108
3119
|
throw new Error(
|
|
@@ -3166,6 +3177,11 @@ function parseLayerRoles(fileNames) {
|
|
|
3166
3177
|
|
|
3167
3178
|
// src/auto-rig/mouth.ts
|
|
3168
3179
|
var UPPER_SHARE = 0.4;
|
|
3180
|
+
var UPPER_THIN = 0.7;
|
|
3181
|
+
var END_TAPER = 0.35;
|
|
3182
|
+
var END_TAPER_FROM = 0.7;
|
|
3183
|
+
var FLICK_PULL = 0.5;
|
|
3184
|
+
var FLICK_SQUASH = 0.25;
|
|
3169
3185
|
var MOUTH_KNOT_PX = 4;
|
|
3170
3186
|
var isLipSet = (byRole) => LIP_ROLES.every((r) => byRole.has(r));
|
|
3171
3187
|
function columnRuns(layer) {
|
|
@@ -3255,9 +3271,17 @@ function mouthOpening(byRole) {
|
|
|
3255
3271
|
const x = x0 + i;
|
|
3256
3272
|
return { ...r, seam: r.Tu + (x - x0) * (x - x1) * (p + q * (x - m)) };
|
|
3257
3273
|
});
|
|
3258
|
-
const
|
|
3274
|
+
const clamp2 = (col) => Math.min(x1, Math.max(x0, col));
|
|
3275
|
+
const base = (col) => columns[clamp2(col) - x0];
|
|
3259
3276
|
const centre = Math.round((x0 + x1) / 2);
|
|
3260
3277
|
const w = base(centre).lineH;
|
|
3278
|
+
const from = END_TAPER_FROM * ((x1 - x0 + 1) / 2);
|
|
3279
|
+
const squash = (col) => {
|
|
3280
|
+
const d = Math.abs(col - centre);
|
|
3281
|
+
const edge = col < centre ? centre - upper.bbox.x : upper.bbox.x + upper.bbox.w - 1 - centre;
|
|
3282
|
+
const t = d <= from ? 0 : d >= edge ? 1 : (d - from) / (edge - from);
|
|
3283
|
+
return 1 - (1 - END_TAPER) * t;
|
|
3284
|
+
};
|
|
3261
3285
|
return {
|
|
3262
3286
|
x0,
|
|
3263
3287
|
x1,
|
|
@@ -3266,39 +3290,80 @@ function mouthOpening(byRole) {
|
|
|
3266
3290
|
w,
|
|
3267
3291
|
at(col) {
|
|
3268
3292
|
const b = base(col);
|
|
3269
|
-
|
|
3293
|
+
const s = squash(clamp2(col));
|
|
3294
|
+
return {
|
|
3295
|
+
...b,
|
|
3296
|
+
overlap: UPPER_THIN * (b.lineH * (1 - s) / 2 + Math.min(w, b.lineH) * s)
|
|
3297
|
+
};
|
|
3298
|
+
},
|
|
3299
|
+
line(col) {
|
|
3300
|
+
if (col >= x0 && col <= x1) {
|
|
3301
|
+
const b = base(col);
|
|
3302
|
+
return { bottom: b.Tu, top: b.lineTop, squash: squash(col) };
|
|
3303
|
+
}
|
|
3304
|
+
const runs = upperCols.get(col);
|
|
3305
|
+
if (runs === void 0) return void 0;
|
|
3306
|
+
return {
|
|
3307
|
+
bottom: edgesOf(upper, runs[runs.length - 1]).bottom,
|
|
3308
|
+
top: edgesOf(upper, runs[0]).top,
|
|
3309
|
+
squash: FLICK_SQUASH
|
|
3310
|
+
};
|
|
3270
3311
|
}
|
|
3271
3312
|
};
|
|
3272
3313
|
}
|
|
3273
3314
|
function mouthFold(role, opening) {
|
|
3315
|
+
const { x0, x1 } = opening;
|
|
3316
|
+
const stackThin = (c, y) => y > c.Tu ? c.Tu + UPPER_THIN * (y - c.Tu) : y;
|
|
3274
3317
|
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
3318
|
const k = 1 - v;
|
|
3319
|
+
const inside = col >= x0 && col <= x1;
|
|
3280
3320
|
switch (role) {
|
|
3281
|
-
case "
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
return (c.seam + c.overlap - c.Bl) * k;
|
|
3285
|
-
|
|
3286
|
-
|
|
3321
|
+
case "lip_lower": {
|
|
3322
|
+
if (!inside) return [0, 0];
|
|
3323
|
+
const c = opening.at(col);
|
|
3324
|
+
return [0, (c.seam + c.overlap - c.Bl) * k];
|
|
3325
|
+
}
|
|
3326
|
+
case "mouth_inner": {
|
|
3327
|
+
const c = opening.at(col);
|
|
3328
|
+
const S = c.seam + c.overlap;
|
|
3329
|
+
return [0, S + v * (stackThin(c, y) - S) - y];
|
|
3330
|
+
}
|
|
3331
|
+
case "mouth_teeth": {
|
|
3332
|
+
const c = opening.at(col);
|
|
3333
|
+
return [0, stackThin(c, y) - y + (c.seam - c.Tu) * k];
|
|
3334
|
+
}
|
|
3335
|
+
case "mouth_tongue": {
|
|
3336
|
+
const c = opening.at(col);
|
|
3337
|
+
return [0, (c.seam + c.overlap - c.Bb) * k];
|
|
3338
|
+
}
|
|
3339
|
+
case "lip_upper": {
|
|
3340
|
+
let c = col;
|
|
3341
|
+
let line = opening.line(c);
|
|
3342
|
+
while (line === void 0)
|
|
3343
|
+
line = opening.line(c += col < x0 ? 1 : -1);
|
|
3344
|
+
const thin = line.bottom + UPPER_THIN * (y - line.bottom);
|
|
3345
|
+
const mid = line.bottom + UPPER_THIN * (line.top - line.bottom) / 2;
|
|
3346
|
+
const travel = inside ? opening.at(col).seam - line.bottom : 0;
|
|
3347
|
+
const shut = mid + line.squash * (thin - mid) + travel;
|
|
3348
|
+
const pull = inside ? 0 : (1 - FLICK_PULL) * ((col < x0 ? x0 : x1 + 1) - col);
|
|
3349
|
+
return [pull * k, thin - y + (shut - thin) * k];
|
|
3350
|
+
}
|
|
3287
3351
|
}
|
|
3288
3352
|
};
|
|
3289
3353
|
}
|
|
3290
|
-
function columnOfKnot(knot, opening
|
|
3354
|
+
function columnOfKnot(knot, opening) {
|
|
3291
3355
|
const boundary = Math.round(knot + opening.canvasW / 2);
|
|
3292
|
-
|
|
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;
|
|
3356
|
+
return boundary === opening.x1 + 1 ? opening.x1 : boundary;
|
|
3295
3357
|
}
|
|
3296
3358
|
function mouthFoldWarp(role, mesh, xs, box, opening) {
|
|
3297
3359
|
const fold = mouthFold(role, opening);
|
|
3298
|
-
return localWarp(
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3360
|
+
return localWarp(
|
|
3361
|
+
P5.MouthOpen,
|
|
3362
|
+
mesh,
|
|
3363
|
+
box,
|
|
3364
|
+
[0, 1],
|
|
3365
|
+
(p, v, i) => fold(columnOfKnot(xs[i % xs.length], opening), p[1], v)
|
|
3366
|
+
);
|
|
3302
3367
|
}
|
|
3303
3368
|
function mouthRestShift(byRole) {
|
|
3304
3369
|
const rig = buildMouthRig(byRole);
|
|
@@ -3311,18 +3376,31 @@ function mouthRestShift(byRole) {
|
|
|
3311
3376
|
fields.set(role, {
|
|
3312
3377
|
box,
|
|
3313
3378
|
xs,
|
|
3314
|
-
|
|
3379
|
+
rows: offsets.length / 2 / xs.length - 1,
|
|
3380
|
+
dy: offsets.filter((_, i) => i % 2 === 1).map((o) => o * bh(box))
|
|
3315
3381
|
});
|
|
3316
3382
|
}
|
|
3317
|
-
const
|
|
3318
|
-
return (role, col) => {
|
|
3319
|
-
const { box, xs, dy } = fields.get(role);
|
|
3320
|
-
const x = col + 0.5 -
|
|
3321
|
-
|
|
3383
|
+
const { canvasW, canvasH } = byRole.get("lip_upper");
|
|
3384
|
+
return (role, col, row) => {
|
|
3385
|
+
const { box, xs, rows, dy } = fields.get(role);
|
|
3386
|
+
const x = col + 0.5 - canvasW / 2;
|
|
3387
|
+
const y = canvasH / 2 - (row + 0.5);
|
|
3388
|
+
if (x < box.x0 || x > box.x1 || y < box.y0 || y > box.y1) return void 0;
|
|
3322
3389
|
let k = 1;
|
|
3323
3390
|
while (k < xs.length - 1 && xs[k] < x) k++;
|
|
3324
|
-
const
|
|
3325
|
-
|
|
3391
|
+
const down = (box.y1 - y) / bh(box) * rows;
|
|
3392
|
+
const r = Math.min(rows - 1, Math.floor(down));
|
|
3393
|
+
const fx = (x - xs[k - 1]) / (xs[k] - xs[k - 1]);
|
|
3394
|
+
const fy = down - r;
|
|
3395
|
+
const at = (i, j) => dy[i * xs.length + j];
|
|
3396
|
+
const tl = at(r, k - 1);
|
|
3397
|
+
const br = at(r + 1, k);
|
|
3398
|
+
if (fx <= fy) {
|
|
3399
|
+
const bl = at(r + 1, k - 1);
|
|
3400
|
+
return tl + fy * (bl - tl) + fx * (br - bl);
|
|
3401
|
+
}
|
|
3402
|
+
const tr = at(r, k);
|
|
3403
|
+
return tl + fx * (tr - tl) + fy * (br - tr);
|
|
3326
3404
|
};
|
|
3327
3405
|
}
|
|
3328
3406
|
function mouthKnots(union, opening) {
|
|
@@ -5917,7 +5995,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
|
|
|
5917
5995
|
headStart = fm.headStart;
|
|
5918
5996
|
} else if (spec.role === "hair_front") {
|
|
5919
5997
|
mesh = columnMesh(b, frontGrid);
|
|
5920
|
-
} else if (lips !== void 0 && isLipRole(spec.role)) {
|
|
5998
|
+
} else if (lips !== void 0 && (isLipRole(spec.role) || isLipInsideRole(spec.role))) {
|
|
5921
5999
|
({ mesh, xs: knotXs } = mouthMesh(b, lips.knots));
|
|
5922
6000
|
} else if (spec.family !== "body") {
|
|
5923
6001
|
const cells = MESH_CELLS[spec.role] ?? FEATURE_MESH_CELLS;
|
|
@@ -6034,6 +6112,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
|
|
|
6034
6112
|
extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
|
|
6035
6113
|
break;
|
|
6036
6114
|
case "mouth_inner":
|
|
6115
|
+
case "mouth_tongue":
|
|
6116
|
+
case "mouth_teeth":
|
|
6037
6117
|
case "lip_lower":
|
|
6038
6118
|
case "lip_upper":
|
|
6039
6119
|
warps.push(
|
|
@@ -6041,6 +6121,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
|
|
|
6041
6121
|
mouthForm(mesh, b, lips.frame),
|
|
6042
6122
|
mouthWiden(mesh, b, lips.frame)
|
|
6043
6123
|
);
|
|
6124
|
+
if (isLipInsideRole(spec.role)) part.clip = { masks: ["mouth_inner"] };
|
|
6044
6125
|
break;
|
|
6045
6126
|
case "blush_L":
|
|
6046
6127
|
case "blush_R":
|
|
@@ -6242,6 +6323,7 @@ export {
|
|
|
6242
6323
|
DeletePhysicsRig,
|
|
6243
6324
|
EditorDocument,
|
|
6244
6325
|
HEAD_BAND,
|
|
6326
|
+
LIP_INSIDE_ROLES,
|
|
6245
6327
|
LIP_ROLES,
|
|
6246
6328
|
LayerGeometryError,
|
|
6247
6329
|
SPECK_CORE_FRACTION,
|
|
@@ -6278,6 +6360,7 @@ export {
|
|
|
6278
6360
|
generateIkiFromLayerSet,
|
|
6279
6361
|
headHalfOf,
|
|
6280
6362
|
interpolateGridOffsets,
|
|
6363
|
+
isLipInsideRole,
|
|
6281
6364
|
isSpeckCore,
|
|
6282
6365
|
mouthFold,
|
|
6283
6366
|
mouthOpening,
|