@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.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 lower lip's skin, the upper
3105
- // lip and its line over both. It folds open instead of crossfading
3106
- // (`mouth.ts`), so it stands in for `mouth`, never beside it.
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 import_format7 = require("@ikijs/format");
3551
+ var import_format8 = require("@ikijs/format");
3236
3552
  function browsY(y) {
3237
3553
  return [
3238
- { parameter: import_format7.StandardParameter.BrowLeftY, value: y, blend: "add" },
3239
- { parameter: import_format7.StandardParameter.BrowRightY, value: y, blend: "add" }
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: import_format7.StandardParameter.BrowLeftAngle, value: innerDown, blend: "add" },
3245
- { parameter: import_format7.StandardParameter.BrowRightAngle, value: -innerDown, blend: "add" }
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: import_format7.StandardParameter.Cheek, value, blend: "add" };
3565
+ return { parameter: import_format8.StandardParameter.Cheek, value, blend: "add" };
3250
3566
  }
3251
- var BROWS = [import_format7.StandardParameter.BrowLeftY, import_format7.StandardParameter.BrowRightY, import_format7.StandardParameter.BrowLeftAngle, import_format7.StandardParameter.BrowRightAngle];
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: import_format7.StandardParameter.MouthForm, value: 1, blend: "add" },
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: import_format7.StandardParameter.EyeOpenLeft, value: 0, blend: "multiply" },
3267
- { parameter: import_format7.StandardParameter.EyeOpenRight, value: 0, blend: "multiply" },
3268
- { parameter: import_format7.StandardParameter.MouthForm, value: 1, blend: "add" },
3269
- { parameter: import_format7.StandardParameter.MouthOpen, value: 0.8, blend: "overwrite" },
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: import_format7.StandardParameter.MouthForm, value: -0.9, blend: "add" }
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: import_format7.StandardParameter.MouthForm, value: -0.8, blend: "add" }
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: import_format7.StandardParameter.MouthOpen, value: 0.9, blend: "overwrite" }
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: [import_format7.StandardParameter.Cheek],
3621
+ needs: [import_format8.StandardParameter.Cheek],
3306
3622
  parameters: [
3307
3623
  cheek(1),
3308
3624
  // Gaze down.
3309
- { parameter: import_format7.StandardParameter.EyeballY, value: -0.65, blend: "add" },
3310
- { parameter: import_format7.StandardParameter.MouthForm, value: 0.4, blend: "add" },
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: import_format7.StandardParameter.AngleY,
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: import_format7.StandardParameter.AngleX,
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: import_format7.StandardParameter.AngleZ,
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: import_format7.StandardParameter.ArmPoseRight,
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: import_format7.StandardParameter.ArmRight,
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: import_format7.StandardParameter.ArmPoseAngleRight,
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 = [import_format7.StandardParameter.ArmRight, import_format7.StandardParameter.ArmPoseRight, import_format7.StandardParameter.ArmPoseAngleRight].every(
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,