@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/dist/index.mjs CHANGED
@@ -3018,9 +3018,16 @@ function forearmPoseDeformer(role, arm) {
3018
3018
  };
3019
3019
  }
3020
3020
 
3021
+ // src/auto-rig/mouth.ts
3022
+ import {
3023
+ StandardParameter as P5
3024
+ } from "@ikijs/format";
3025
+
3021
3026
  // src/auto-rig/roles.ts
3022
3027
  var LIP_ROLES = ["mouth_inner", "lip_lower", "lip_upper"];
3023
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);
3024
3031
  var ROLE_TABLE = [
3025
3032
  { role: "hair_back", family: "hair_back" },
3026
3033
  { role: "body", family: "body" },
@@ -3032,10 +3039,13 @@ var ROLE_TABLE = [
3032
3039
  { role: "nose", family: "nose" },
3033
3040
  { role: "mouth", family: "mouth" },
3034
3041
  { role: "mouth_open", family: "mouth" },
3035
- // The lip set, back to front: the interior, the lower lip's skin, the upper
3036
- // lip and its line over both. It folds open instead of crossfading
3037
- // (`mouth.ts`), so it stands in for `mouth`, never beside it.
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.
3038
3046
  { role: "mouth_inner", family: "mouth" },
3047
+ { role: "mouth_tongue", family: "mouth" },
3048
+ { role: "mouth_teeth", family: "mouth" },
3039
3049
  { role: "lip_lower", family: "mouth" },
3040
3050
  { role: "lip_upper", family: "mouth" },
3041
3051
  { role: "eye_L", family: "eye_L", required: true },
@@ -3098,6 +3108,12 @@ function checkArmsHaveBody(roles) {
3098
3108
  function checkMouth(roles) {
3099
3109
  const has = new Set(roles);
3100
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
+ }
3101
3117
  if (present.length === 0) {
3102
3118
  if (!has.has("mouth")) {
3103
3119
  throw new Error(
@@ -3159,6 +3175,301 @@ function parseLayerRoles(fileNames) {
3159
3175
  return out;
3160
3176
  }
3161
3177
 
3178
+ // src/auto-rig/mouth.ts
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;
3185
+ var MOUTH_KNOT_PX = 4;
3186
+ var isLipSet = (byRole) => LIP_ROLES.every((r) => byRole.has(r));
3187
+ function columnRuns(layer) {
3188
+ const cols = /* @__PURE__ */ new Map();
3189
+ (layer.rowRuns ?? []).forEach((runs, k) => {
3190
+ for (let i = 0; i < runs.length; i += 2) {
3191
+ for (let c = runs[i]; c < runs[i + 1]; c++) {
3192
+ const list = cols.get(c) ?? [];
3193
+ const last = list[list.length - 1];
3194
+ if (last !== void 0 && last[1] === k) last[1] = k + 1;
3195
+ else list.push([k, k + 1]);
3196
+ cols.set(c, list);
3197
+ }
3198
+ }
3199
+ });
3200
+ return cols;
3201
+ }
3202
+ var edgesOf = (layer, run) => ({
3203
+ top: layer.canvasH / 2 - (layer.bbox.y + run[0]),
3204
+ bottom: layer.canvasH / 2 - (layer.bbox.y + run[1])
3205
+ });
3206
+ function mouthOpening(byRole) {
3207
+ const inner = byRole.get("mouth_inner");
3208
+ const upper = byRole.get("lip_upper");
3209
+ const lower = byRole.get("lip_lower");
3210
+ const innerCols = columnRuns(inner);
3211
+ if (innerCols.size === 0) {
3212
+ throw new LayerGeometryError(
3213
+ `auto-rig: layer "${inner.fileName}": mouth_inner has no opaque pixel (alpha \u2265 128) to read the opening off`
3214
+ );
3215
+ }
3216
+ const upperCols = columnRuns(upper);
3217
+ const lowerCols = columnRuns(lower);
3218
+ const cols = [...innerCols.keys()];
3219
+ const x0 = Math.min(...cols);
3220
+ const x1 = Math.max(...cols);
3221
+ const read = (col) => {
3222
+ const runs = innerCols.get(col);
3223
+ if (runs === void 0) return void 0;
3224
+ const T = edgesOf(inner, runs[0]).top;
3225
+ const Bb = edgesOf(inner, runs[runs.length - 1]).bottom;
3226
+ const line = [...upperCols.get(col) ?? []].reverse().map((r) => edgesOf(upper, r)).find((e) => e.top >= T);
3227
+ const Tu = line?.bottom ?? T;
3228
+ const lineTop = line?.top ?? T;
3229
+ const skin = lowerCols.get(col)?.[0];
3230
+ return {
3231
+ T,
3232
+ Bb,
3233
+ Tu,
3234
+ lineTop,
3235
+ lineH: lineTop - Tu,
3236
+ Bl: skin === void 0 ? Bb : edgesOf(lower, skin).top,
3237
+ H: T - Bb
3238
+ };
3239
+ };
3240
+ const reads = [];
3241
+ for (let c = x0; c <= x1; c++) {
3242
+ let r;
3243
+ for (let d = 0; (r = read(c - d) ?? read(c + d)) === void 0; d++) ;
3244
+ reads.push(r);
3245
+ }
3246
+ const m = (x0 + x1) / 2;
3247
+ let p = 0;
3248
+ let q = 0;
3249
+ if (reads.length >= 4) {
3250
+ let s11 = 0;
3251
+ let s12 = 0;
3252
+ let s22 = 0;
3253
+ let t1 = 0;
3254
+ let t2 = 0;
3255
+ reads.forEach((r, i) => {
3256
+ const x = x0 + i;
3257
+ const b1 = (x - x0) * (x - x1);
3258
+ const b2 = b1 * (x - m);
3259
+ const d = r.T - UPPER_SHARE * r.H - r.Tu;
3260
+ s11 += b1 * b1;
3261
+ s12 += b1 * b2;
3262
+ s22 += b2 * b2;
3263
+ t1 += b1 * d;
3264
+ t2 += b2 * d;
3265
+ });
3266
+ const det = s11 * s22 - s12 * s12;
3267
+ p = (t1 * s22 - t2 * s12) / det;
3268
+ q = (s11 * t2 - s12 * t1) / det;
3269
+ }
3270
+ const columns = reads.map((r, i) => {
3271
+ const x = x0 + i;
3272
+ return { ...r, seam: r.Tu + (x - x0) * (x - x1) * (p + q * (x - m)) };
3273
+ });
3274
+ const clamp2 = (col) => Math.min(x1, Math.max(x0, col));
3275
+ const base = (col) => columns[clamp2(col) - x0];
3276
+ const centre = Math.round((x0 + x1) / 2);
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
+ };
3285
+ return {
3286
+ x0,
3287
+ x1,
3288
+ centre,
3289
+ canvasW: inner.canvasW,
3290
+ w,
3291
+ at(col) {
3292
+ const b = base(col);
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
+ };
3311
+ }
3312
+ };
3313
+ }
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;
3317
+ return (col, y, v) => {
3318
+ const k = 1 - v;
3319
+ const inside = col >= x0 && col <= x1;
3320
+ switch (role) {
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
+ }
3351
+ }
3352
+ };
3353
+ }
3354
+ function columnOfKnot(knot, opening) {
3355
+ const boundary = Math.round(knot + opening.canvasW / 2);
3356
+ return boundary === opening.x1 + 1 ? opening.x1 : boundary;
3357
+ }
3358
+ function mouthFoldWarp(role, mesh, xs, box, opening) {
3359
+ const fold = mouthFold(role, opening);
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
+ );
3367
+ }
3368
+ function mouthRestShift(byRole) {
3369
+ const rig = buildMouthRig(byRole);
3370
+ const fields = /* @__PURE__ */ new Map();
3371
+ for (const role of ["lip_upper", "lip_lower"]) {
3372
+ const box = boxOfLayer(byRole.get(role));
3373
+ const { mesh, xs } = mouthMesh(box, rig.knots);
3374
+ const warp = mouthFoldWarp(role, mesh, xs, box, rig.opening);
3375
+ const offsets = warp.keyforms[0].offsets;
3376
+ fields.set(role, {
3377
+ box,
3378
+ xs,
3379
+ rows: offsets.length / 2 / xs.length - 1,
3380
+ dy: offsets.filter((_, i) => i % 2 === 1).map((o) => o * bh(box))
3381
+ });
3382
+ }
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;
3389
+ let k = 1;
3390
+ while (k < xs.length - 1 && xs[k] < x) k++;
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);
3404
+ };
3405
+ }
3406
+ function mouthKnots(union, opening) {
3407
+ const half = opening.canvasW / 2;
3408
+ const step = MOUTH_KNOT_PX;
3409
+ const xs = [union.x0];
3410
+ while (xs[xs.length - 1] + step < union.x1 - 0.3 * step)
3411
+ xs.push(xs[xs.length - 1] + step);
3412
+ xs.push(union.x1);
3413
+ const fixed = /* @__PURE__ */ new Set([union.x0, union.x1]);
3414
+ for (const x of [
3415
+ opening.x0 - half,
3416
+ opening.x1 - half,
3417
+ opening.x1 + 1 - half
3418
+ ]) {
3419
+ fixed.add(x);
3420
+ if (xs.includes(x)) continue;
3421
+ const near = xs.findIndex((v) => Math.abs(v - x) < 0.3 * step);
3422
+ if (near >= 0 && !fixed.has(xs[near])) xs[near] = x;
3423
+ else xs.push(x);
3424
+ }
3425
+ xs.sort((p, q) => p - q);
3426
+ for (const x of xs) {
3427
+ if (!Number.isInteger(x + half)) {
3428
+ throw new Error(
3429
+ `auto-rig: mouth knot ${x} is not an integer canvas boundary`
3430
+ );
3431
+ }
3432
+ }
3433
+ return xs;
3434
+ }
3435
+ function mouthMesh(box, knots) {
3436
+ const xs = [box.x0, ...knots.filter((k) => k > box.x0 && k < box.x1), box.x1];
3437
+ const rows = cellsFor(bh(box), MOUTH_KNOT_PX, 2, 40);
3438
+ const ys = Array.from(
3439
+ { length: rows + 1 },
3440
+ (_, r) => box.y1 - bh(box) * r / rows
3441
+ );
3442
+ return { mesh: columnMesh(box, { xs, ys }), xs };
3443
+ }
3444
+ function buildMouthRig(byRole) {
3445
+ const opening = mouthOpening(byRole);
3446
+ const union = unionBoxes(LIP_ROLES.map((r) => boxOfLayer(byRole.get(r))));
3447
+ return {
3448
+ opening,
3449
+ frame: mouthFrameOf(union),
3450
+ knots: mouthKnots(union, opening),
3451
+ union
3452
+ };
3453
+ }
3454
+ function mouthAnchor(byRole) {
3455
+ const box = (role) => boxOfLayer(byRole.get(role));
3456
+ if (isLipSet(byRole)) {
3457
+ const opening = mouthOpening(byRole);
3458
+ return {
3459
+ at: {
3460
+ x: cx(box("mouth_inner")),
3461
+ y: opening.at(opening.centre).seam
3462
+ },
3463
+ box: unionBoxes(LIP_ROLES.map(box))
3464
+ };
3465
+ }
3466
+ const m = box("mouth");
3467
+ return {
3468
+ at: { x: cx(m), y: cy(m) },
3469
+ box: byRole.has("mouth_open") ? unionBoxes([m, box("mouth_open")]) : m
3470
+ };
3471
+ }
3472
+
3162
3473
  // src/auto-rig/generate.ts
3163
3474
  import {
3164
3475
  IKI_FORMAT_VERSION as IKI_FORMAT_VERSION2,
@@ -3168,30 +3479,30 @@ import {
3168
3479
 
3169
3480
  // src/auto-rig/animations.ts
3170
3481
  import {
3171
- StandardParameter as P5
3482
+ StandardParameter as P6
3172
3483
  } from "@ikijs/format";
3173
3484
  function browsY(y) {
3174
3485
  return [
3175
- { parameter: P5.BrowLeftY, value: y, blend: "add" },
3176
- { parameter: P5.BrowRightY, value: y, blend: "add" }
3486
+ { parameter: P6.BrowLeftY, value: y, blend: "add" },
3487
+ { parameter: P6.BrowRightY, value: y, blend: "add" }
3177
3488
  ];
3178
3489
  }
3179
3490
  function browsTilt(innerDown) {
3180
3491
  return [
3181
- { parameter: P5.BrowLeftAngle, value: innerDown, blend: "add" },
3182
- { parameter: P5.BrowRightAngle, value: -innerDown, blend: "add" }
3492
+ { parameter: P6.BrowLeftAngle, value: innerDown, blend: "add" },
3493
+ { parameter: P6.BrowRightAngle, value: -innerDown, blend: "add" }
3183
3494
  ];
3184
3495
  }
3185
3496
  function cheek(value) {
3186
- return { parameter: P5.Cheek, value, blend: "add" };
3497
+ return { parameter: P6.Cheek, value, blend: "add" };
3187
3498
  }
3188
- var BROWS = [P5.BrowLeftY, P5.BrowRightY, P5.BrowLeftAngle, P5.BrowRightAngle];
3499
+ var BROWS = [P6.BrowLeftY, P6.BrowRightY, P6.BrowLeftAngle, P6.BrowRightAngle];
3189
3500
  var EXPRESSIONS = [
3190
3501
  {
3191
3502
  id: "smile",
3192
3503
  description: "Smiling and pleased: warm, friendly, content. For greetings, thanks, agreement and gentle happiness.",
3193
3504
  parameters: [
3194
- { parameter: P5.MouthForm, value: 1, blend: "add" },
3505
+ { parameter: P6.MouthForm, value: 1, blend: "add" },
3195
3506
  ...browsY(0.25),
3196
3507
  cheek(0.4)
3197
3508
  ]
@@ -3200,10 +3511,10 @@ var EXPRESSIONS = [
3200
3511
  id: "laugh",
3201
3512
  description: "Laughing, eyes shut and mouth open: delighted, amused. For jokes, playfulness and big joy.",
3202
3513
  parameters: [
3203
- { parameter: P5.EyeOpenLeft, value: 0, blend: "multiply" },
3204
- { parameter: P5.EyeOpenRight, value: 0, blend: "multiply" },
3205
- { parameter: P5.MouthForm, value: 1, blend: "add" },
3206
- { parameter: P5.MouthOpen, value: 0.8, blend: "overwrite" },
3514
+ { parameter: P6.EyeOpenLeft, value: 0, blend: "multiply" },
3515
+ { parameter: P6.EyeOpenRight, value: 0, blend: "multiply" },
3516
+ { parameter: P6.MouthForm, value: 1, blend: "add" },
3517
+ { parameter: P6.MouthOpen, value: 0.8, blend: "overwrite" },
3207
3518
  ...browsY(0.4),
3208
3519
  cheek(0.65)
3209
3520
  ]
@@ -3215,7 +3526,7 @@ var EXPRESSIONS = [
3215
3526
  parameters: [
3216
3527
  ...browsY(-0.5),
3217
3528
  ...browsTilt(0.8),
3218
- { parameter: P5.MouthForm, value: -0.9, blend: "add" }
3529
+ { parameter: P6.MouthForm, value: -0.9, blend: "add" }
3219
3530
  ]
3220
3531
  },
3221
3532
  {
@@ -3225,7 +3536,7 @@ var EXPRESSIONS = [
3225
3536
  parameters: [
3226
3537
  ...browsY(0.35),
3227
3538
  ...browsTilt(-0.8),
3228
- { parameter: P5.MouthForm, value: -0.8, blend: "add" }
3539
+ { parameter: P6.MouthForm, value: -0.8, blend: "add" }
3229
3540
  ]
3230
3541
  },
3231
3542
  {
@@ -3233,18 +3544,18 @@ var EXPRESSIONS = [
3233
3544
  description: "Surprised, mouth dropped open: startled, amazed. For shock, sudden news or disbelief.",
3234
3545
  parameters: [
3235
3546
  ...browsY(0.8),
3236
- { parameter: P5.MouthOpen, value: 0.9, blend: "overwrite" }
3547
+ { parameter: P6.MouthOpen, value: 0.9, blend: "overwrite" }
3237
3548
  ]
3238
3549
  },
3239
3550
  {
3240
3551
  id: "shy",
3241
3552
  description: "Shy or embarrassed: bashful, flustered. For being praised, teased or caught off guard.",
3242
- needs: [P5.Cheek],
3553
+ needs: [P6.Cheek],
3243
3554
  parameters: [
3244
3555
  cheek(1),
3245
3556
  // Gaze down.
3246
- { parameter: P5.EyeballY, value: -0.65, blend: "add" },
3247
- { parameter: P5.MouthForm, value: 0.4, blend: "add" },
3557
+ { parameter: P6.EyeballY, value: -0.65, blend: "add" },
3558
+ { parameter: P6.MouthForm, value: 0.4, blend: "add" },
3248
3559
  ...browsTilt(-0.25)
3249
3560
  ]
3250
3561
  }
@@ -3264,7 +3575,7 @@ var HEAD_MOTIONS = {
3264
3575
  duration: 1,
3265
3576
  curves: [
3266
3577
  {
3267
- parameter: P5.AngleY,
3578
+ parameter: P6.AngleY,
3268
3579
  keys: [
3269
3580
  [0, 0],
3270
3581
  [0.3, -18],
@@ -3285,7 +3596,7 @@ var HEAD_MOTIONS = {
3285
3596
  fadeIn: 0.15,
3286
3597
  curves: [
3287
3598
  {
3288
- parameter: P5.AngleX,
3599
+ parameter: P6.AngleX,
3289
3600
  keys: [
3290
3601
  [0, 0],
3291
3602
  [0.2, -18],
@@ -3304,7 +3615,7 @@ var HEAD_MOTIONS = {
3304
3615
  duration: 1.4,
3305
3616
  curves: [
3306
3617
  {
3307
- parameter: P5.AngleZ,
3618
+ parameter: P6.AngleZ,
3308
3619
  keys: [
3309
3620
  [0, 0],
3310
3621
  [0.35, 20],
@@ -3325,7 +3636,7 @@ var WAVE = {
3325
3636
  fadeOut: 0.15,
3326
3637
  curves: [
3327
3638
  {
3328
- parameter: P5.ArmPoseRight,
3639
+ parameter: P6.ArmPoseRight,
3329
3640
  interpolation: "linear",
3330
3641
  keys: [
3331
3642
  [0, 0],
@@ -3335,7 +3646,7 @@ var WAVE = {
3335
3646
  ]
3336
3647
  },
3337
3648
  {
3338
- parameter: P5.ArmRight,
3649
+ parameter: P6.ArmRight,
3339
3650
  keys: [
3340
3651
  [0, 0],
3341
3652
  [0.4, 10],
@@ -3344,7 +3655,7 @@ var WAVE = {
3344
3655
  ]
3345
3656
  },
3346
3657
  {
3347
- parameter: P5.ArmPoseAngleRight,
3658
+ parameter: P6.ArmPoseAngleRight,
3348
3659
  keys: [
3349
3660
  [0, 0],
3350
3661
  [0.5, 0],
@@ -3360,7 +3671,7 @@ var WAVE = {
3360
3671
  ]
3361
3672
  };
3362
3673
  function defaultMotions(declared) {
3363
- const waves = [P5.ArmRight, P5.ArmPoseRight, P5.ArmPoseAngleRight].every(
3674
+ const waves = [P6.ArmRight, P6.ArmPoseRight, P6.ArmPoseAngleRight].every(
3364
3675
  (id) => declared.has(id)
3365
3676
  );
3366
3677
  return waves ? { ...HEAD_MOTIONS, ...WAVE } : HEAD_MOTIONS;
@@ -3598,181 +3909,6 @@ function checkStrandEdges(strand, irises, hair) {
3598
3909
  // src/auto-rig/context.ts
3599
3910
  import { StandardParameter as P7 } from "@ikijs/format";
3600
3911
 
3601
- // src/auto-rig/mouth.ts
3602
- import {
3603
- StandardParameter as P6
3604
- } from "@ikijs/format";
3605
- var UPPER_SHARE = 0.3;
3606
- var MOUTH_KNOT_PX = 4;
3607
- var isLipSet = (byRole) => LIP_ROLES.every((r) => byRole.has(r));
3608
- function columnRuns(layer) {
3609
- const cols = /* @__PURE__ */ new Map();
3610
- (layer.rowRuns ?? []).forEach((runs, k) => {
3611
- for (let i = 0; i < runs.length; i += 2) {
3612
- for (let c = runs[i]; c < runs[i + 1]; c++) {
3613
- const list = cols.get(c) ?? [];
3614
- const last = list[list.length - 1];
3615
- if (last !== void 0 && last[1] === k) last[1] = k + 1;
3616
- else list.push([k, k + 1]);
3617
- cols.set(c, list);
3618
- }
3619
- }
3620
- });
3621
- return cols;
3622
- }
3623
- var edgesOf = (layer, run) => ({
3624
- top: layer.canvasH / 2 - (layer.bbox.y + run[0]),
3625
- bottom: layer.canvasH / 2 - (layer.bbox.y + run[1])
3626
- });
3627
- function mouthOpening(byRole) {
3628
- const inner = byRole.get("mouth_inner");
3629
- const upper = byRole.get("lip_upper");
3630
- const lower = byRole.get("lip_lower");
3631
- const innerCols = columnRuns(inner);
3632
- if (innerCols.size === 0) {
3633
- throw new LayerGeometryError(
3634
- `auto-rig: layer "${inner.fileName}": mouth_inner has no opaque pixel (alpha \u2265 128) to read the opening off`
3635
- );
3636
- }
3637
- const upperCols = columnRuns(upper);
3638
- const lowerCols = columnRuns(lower);
3639
- const cols = [...innerCols.keys()];
3640
- const x0 = Math.min(...cols);
3641
- const x1 = Math.max(...cols);
3642
- const read = (col) => {
3643
- const runs = innerCols.get(col);
3644
- if (runs === void 0) return void 0;
3645
- const T = edgesOf(inner, runs[0]).top;
3646
- const Bb = edgesOf(inner, runs[runs.length - 1]).bottom;
3647
- const line = [...upperCols.get(col) ?? []].reverse().map((r) => edgesOf(upper, r)).find((e) => e.top >= T);
3648
- const Tu = line?.bottom ?? T;
3649
- const lineTop = line?.top ?? T;
3650
- const skin = lowerCols.get(col)?.[0];
3651
- return {
3652
- T,
3653
- Bb,
3654
- Tu,
3655
- lineTop,
3656
- lineH: lineTop - Tu,
3657
- Bl: skin === void 0 ? Bb : edgesOf(lower, skin).top,
3658
- H: T - Bb,
3659
- seam: T - UPPER_SHARE * (T - Bb)
3660
- };
3661
- };
3662
- const reads = [];
3663
- for (let c = x0; c <= x1; c++) {
3664
- let r;
3665
- for (let d = 0; (r = read(c - d) ?? read(c + d)) === void 0; d++) ;
3666
- reads.push(r);
3667
- }
3668
- const base = (col) => reads[Math.min(x1, Math.max(x0, col)) - x0];
3669
- const centre = Math.round((x0 + x1) / 2);
3670
- const w = base(centre).lineH;
3671
- return {
3672
- x0,
3673
- x1,
3674
- centre,
3675
- canvasW: inner.canvasW,
3676
- w,
3677
- at(col) {
3678
- const b = base(col);
3679
- return { ...b, overlap: Math.max(0, Math.min(w, b.lineH)) };
3680
- }
3681
- };
3682
- }
3683
- function mouthFold(role, opening) {
3684
- return (col, y, v) => {
3685
- if (v === 1) return 0;
3686
- if (role !== "mouth_inner" && (col < opening.x0 || col > opening.x1))
3687
- return 0;
3688
- const c = opening.at(col);
3689
- const k = 1 - v;
3690
- switch (role) {
3691
- case "lip_upper":
3692
- return (c.seam - c.Tu) * k;
3693
- case "lip_lower":
3694
- return (c.seam + c.overlap - c.Bl) * k;
3695
- case "mouth_inner":
3696
- return (c.seam + c.overlap - y) * k;
3697
- }
3698
- };
3699
- }
3700
- function columnOfKnot(knot, opening, role) {
3701
- const boundary = Math.round(knot + opening.canvasW / 2);
3702
- const col = boundary === opening.x1 + 1 ? opening.x1 : boundary;
3703
- if (col >= opening.x0 && col <= opening.x1) return col;
3704
- return role === "mouth_inner" ? Math.min(opening.x1, Math.max(opening.x0, col)) : void 0;
3705
- }
3706
- function mouthFoldWarp(role, mesh, xs, box, opening) {
3707
- const fold = mouthFold(role, opening);
3708
- return localWarp(P6.MouthOpen, mesh, box, [0, 1], (p, v, i) => {
3709
- const col = columnOfKnot(xs[i % xs.length], opening, role);
3710
- return [0, col === void 0 ? 0 : fold(col, p[1], v)];
3711
- });
3712
- }
3713
- function mouthKnots(union, opening) {
3714
- const half = opening.canvasW / 2;
3715
- const step = MOUTH_KNOT_PX;
3716
- const xs = [union.x0];
3717
- while (xs[xs.length - 1] + step < union.x1 - 0.3 * step)
3718
- xs.push(xs[xs.length - 1] + step);
3719
- xs.push(union.x1);
3720
- const fixed = /* @__PURE__ */ new Set([union.x0, union.x1]);
3721
- for (const x of [opening.x0 - half, opening.x1 + 1 - half]) {
3722
- fixed.add(x);
3723
- if (xs.includes(x)) continue;
3724
- const near = xs.findIndex((v) => Math.abs(v - x) < 0.3 * step);
3725
- if (near >= 0 && !fixed.has(xs[near])) xs[near] = x;
3726
- else xs.push(x);
3727
- }
3728
- xs.sort((p, q) => p - q);
3729
- for (const x of xs) {
3730
- if (!Number.isInteger(x + half)) {
3731
- throw new Error(
3732
- `auto-rig: mouth knot ${x} is not an integer canvas boundary`
3733
- );
3734
- }
3735
- }
3736
- return xs;
3737
- }
3738
- function mouthMesh(box, knots) {
3739
- const xs = [box.x0, ...knots.filter((k) => k > box.x0 && k < box.x1), box.x1];
3740
- const rows = cellsFor(bh(box), MOUTH_KNOT_PX, 2, 40);
3741
- const ys = Array.from(
3742
- { length: rows + 1 },
3743
- (_, r) => box.y1 - bh(box) * r / rows
3744
- );
3745
- return { mesh: columnMesh(box, { xs, ys }), xs };
3746
- }
3747
- function buildMouthRig(byRole) {
3748
- const opening = mouthOpening(byRole);
3749
- const union = unionBoxes(LIP_ROLES.map((r) => boxOfLayer(byRole.get(r))));
3750
- return {
3751
- opening,
3752
- frame: mouthFrameOf(union),
3753
- knots: mouthKnots(union, opening),
3754
- union
3755
- };
3756
- }
3757
- function mouthAnchor(byRole) {
3758
- const box = (role) => boxOfLayer(byRole.get(role));
3759
- if (isLipSet(byRole)) {
3760
- const opening = mouthOpening(byRole);
3761
- return {
3762
- at: {
3763
- x: cx(box("mouth_inner")),
3764
- y: opening.at(opening.centre).seam
3765
- },
3766
- box: unionBoxes(LIP_ROLES.map(box))
3767
- };
3768
- }
3769
- const m = box("mouth");
3770
- return {
3771
- at: { x: cx(m), y: cy(m) },
3772
- box: byRole.has("mouth_open") ? unionBoxes([m, box("mouth_open")]) : m
3773
- };
3774
- }
3775
-
3776
3912
  // src/auto-rig/head.ts
3777
3913
  function outerEdge(runs, axisX, side) {
3778
3914
  if (runs.length === 0) return void 0;
@@ -5859,7 +5995,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
5859
5995
  headStart = fm.headStart;
5860
5996
  } else if (spec.role === "hair_front") {
5861
5997
  mesh = columnMesh(b, frontGrid);
5862
- } else if (lips !== void 0 && isLipRole(spec.role)) {
5998
+ } else if (lips !== void 0 && (isLipRole(spec.role) || isLipInsideRole(spec.role))) {
5863
5999
  ({ mesh, xs: knotXs } = mouthMesh(b, lips.knots));
5864
6000
  } else if (spec.family !== "body") {
5865
6001
  const cells = MESH_CELLS[spec.role] ?? FEATURE_MESH_CELLS;
@@ -5976,6 +6112,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
5976
6112
  extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
5977
6113
  break;
5978
6114
  case "mouth_inner":
6115
+ case "mouth_tongue":
6116
+ case "mouth_teeth":
5979
6117
  case "lip_lower":
5980
6118
  case "lip_upper":
5981
6119
  warps.push(
@@ -5983,6 +6121,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
5983
6121
  mouthForm(mesh, b, lips.frame),
5984
6122
  mouthWiden(mesh, b, lips.frame)
5985
6123
  );
6124
+ if (isLipInsideRole(spec.role)) part.clip = { masks: ["mouth_inner"] };
5986
6125
  break;
5987
6126
  case "blush_L":
5988
6127
  case "blush_R":
@@ -6184,6 +6323,8 @@ export {
6184
6323
  DeletePhysicsRig,
6185
6324
  EditorDocument,
6186
6325
  HEAD_BAND,
6326
+ LIP_INSIDE_ROLES,
6327
+ LIP_ROLES,
6187
6328
  LayerGeometryError,
6188
6329
  SPECK_CORE_FRACTION,
6189
6330
  SetDeformerBindings,
@@ -6205,6 +6346,7 @@ export {
6205
6346
  UV_INSET_PX,
6206
6347
  armGeometry,
6207
6348
  captureBindingEndpoint,
6349
+ columnRuns,
6208
6350
  computeGridOffsets,
6209
6351
  createDefaultMatrixDeformer,
6210
6352
  createDefaultPart,
@@ -6218,7 +6360,11 @@ export {
6218
6360
  generateIkiFromLayerSet,
6219
6361
  headHalfOf,
6220
6362
  interpolateGridOffsets,
6363
+ isLipInsideRole,
6221
6364
  isSpeckCore,
6365
+ mouthFold,
6366
+ mouthOpening,
6367
+ mouthRestShift,
6222
6368
  packAtlas,
6223
6369
  parseLayerRoles,
6224
6370
  partIdsOfRole,