@ikijs/editor 0.21.0 → 0.22.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,6 +3018,11 @@ 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);
@@ -3159,6 +3164,234 @@ function parseLayerRoles(fileNames) {
3159
3164
  return out;
3160
3165
  }
3161
3166
 
3167
+ // src/auto-rig/mouth.ts
3168
+ var UPPER_SHARE = 0.4;
3169
+ var MOUTH_KNOT_PX = 4;
3170
+ var isLipSet = (byRole) => LIP_ROLES.every((r) => byRole.has(r));
3171
+ function columnRuns(layer) {
3172
+ const cols = /* @__PURE__ */ new Map();
3173
+ (layer.rowRuns ?? []).forEach((runs, k) => {
3174
+ for (let i = 0; i < runs.length; i += 2) {
3175
+ for (let c = runs[i]; c < runs[i + 1]; c++) {
3176
+ const list = cols.get(c) ?? [];
3177
+ const last = list[list.length - 1];
3178
+ if (last !== void 0 && last[1] === k) last[1] = k + 1;
3179
+ else list.push([k, k + 1]);
3180
+ cols.set(c, list);
3181
+ }
3182
+ }
3183
+ });
3184
+ return cols;
3185
+ }
3186
+ var edgesOf = (layer, run) => ({
3187
+ top: layer.canvasH / 2 - (layer.bbox.y + run[0]),
3188
+ bottom: layer.canvasH / 2 - (layer.bbox.y + run[1])
3189
+ });
3190
+ function mouthOpening(byRole) {
3191
+ const inner = byRole.get("mouth_inner");
3192
+ const upper = byRole.get("lip_upper");
3193
+ const lower = byRole.get("lip_lower");
3194
+ const innerCols = columnRuns(inner);
3195
+ if (innerCols.size === 0) {
3196
+ throw new LayerGeometryError(
3197
+ `auto-rig: layer "${inner.fileName}": mouth_inner has no opaque pixel (alpha \u2265 128) to read the opening off`
3198
+ );
3199
+ }
3200
+ const upperCols = columnRuns(upper);
3201
+ const lowerCols = columnRuns(lower);
3202
+ const cols = [...innerCols.keys()];
3203
+ const x0 = Math.min(...cols);
3204
+ const x1 = Math.max(...cols);
3205
+ const read = (col) => {
3206
+ const runs = innerCols.get(col);
3207
+ if (runs === void 0) return void 0;
3208
+ const T = edgesOf(inner, runs[0]).top;
3209
+ const Bb = edgesOf(inner, runs[runs.length - 1]).bottom;
3210
+ const line = [...upperCols.get(col) ?? []].reverse().map((r) => edgesOf(upper, r)).find((e) => e.top >= T);
3211
+ const Tu = line?.bottom ?? T;
3212
+ const lineTop = line?.top ?? T;
3213
+ const skin = lowerCols.get(col)?.[0];
3214
+ return {
3215
+ T,
3216
+ Bb,
3217
+ Tu,
3218
+ lineTop,
3219
+ lineH: lineTop - Tu,
3220
+ Bl: skin === void 0 ? Bb : edgesOf(lower, skin).top,
3221
+ H: T - Bb
3222
+ };
3223
+ };
3224
+ const reads = [];
3225
+ for (let c = x0; c <= x1; c++) {
3226
+ let r;
3227
+ for (let d = 0; (r = read(c - d) ?? read(c + d)) === void 0; d++) ;
3228
+ reads.push(r);
3229
+ }
3230
+ const m = (x0 + x1) / 2;
3231
+ let p = 0;
3232
+ let q = 0;
3233
+ if (reads.length >= 4) {
3234
+ let s11 = 0;
3235
+ let s12 = 0;
3236
+ let s22 = 0;
3237
+ let t1 = 0;
3238
+ let t2 = 0;
3239
+ reads.forEach((r, i) => {
3240
+ const x = x0 + i;
3241
+ const b1 = (x - x0) * (x - x1);
3242
+ const b2 = b1 * (x - m);
3243
+ const d = r.T - UPPER_SHARE * r.H - r.Tu;
3244
+ s11 += b1 * b1;
3245
+ s12 += b1 * b2;
3246
+ s22 += b2 * b2;
3247
+ t1 += b1 * d;
3248
+ t2 += b2 * d;
3249
+ });
3250
+ const det = s11 * s22 - s12 * s12;
3251
+ p = (t1 * s22 - t2 * s12) / det;
3252
+ q = (s11 * t2 - s12 * t1) / det;
3253
+ }
3254
+ const columns = reads.map((r, i) => {
3255
+ const x = x0 + i;
3256
+ return { ...r, seam: r.Tu + (x - x0) * (x - x1) * (p + q * (x - m)) };
3257
+ });
3258
+ const base = (col) => columns[Math.min(x1, Math.max(x0, col)) - x0];
3259
+ const centre = Math.round((x0 + x1) / 2);
3260
+ const w = base(centre).lineH;
3261
+ return {
3262
+ x0,
3263
+ x1,
3264
+ centre,
3265
+ canvasW: inner.canvasW,
3266
+ w,
3267
+ at(col) {
3268
+ const b = base(col);
3269
+ return { ...b, overlap: Math.max(0, Math.min(w, b.lineH)) };
3270
+ }
3271
+ };
3272
+ }
3273
+ function mouthFold(role, opening) {
3274
+ 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
+ const k = 1 - v;
3280
+ switch (role) {
3281
+ case "lip_upper":
3282
+ return (c.seam - c.Tu) * k;
3283
+ case "lip_lower":
3284
+ return (c.seam + c.overlap - c.Bl) * k;
3285
+ case "mouth_inner":
3286
+ return (c.seam + c.overlap - y) * k;
3287
+ }
3288
+ };
3289
+ }
3290
+ function columnOfKnot(knot, opening, role) {
3291
+ const boundary = Math.round(knot + opening.canvasW / 2);
3292
+ const col = boundary === opening.x1 + 1 ? opening.x1 : boundary;
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;
3295
+ }
3296
+ function mouthFoldWarp(role, mesh, xs, box, opening) {
3297
+ const fold = mouthFold(role, opening);
3298
+ return localWarp(P5.MouthOpen, mesh, box, [0, 1], (p, v, i) => {
3299
+ const col = columnOfKnot(xs[i % xs.length], opening, role);
3300
+ return [0, col === void 0 ? 0 : fold(col, p[1], v)];
3301
+ });
3302
+ }
3303
+ function mouthRestShift(byRole) {
3304
+ const rig = buildMouthRig(byRole);
3305
+ const fields = /* @__PURE__ */ new Map();
3306
+ for (const role of ["lip_upper", "lip_lower"]) {
3307
+ const box = boxOfLayer(byRole.get(role));
3308
+ const { mesh, xs } = mouthMesh(box, rig.knots);
3309
+ const warp = mouthFoldWarp(role, mesh, xs, box, rig.opening);
3310
+ const offsets = warp.keyforms[0].offsets;
3311
+ fields.set(role, {
3312
+ box,
3313
+ xs,
3314
+ dy: xs.map((_, i) => offsets[2 * i + 1] * bh(box))
3315
+ });
3316
+ }
3317
+ const half = rig.opening.canvasW / 2;
3318
+ return (role, col) => {
3319
+ const { box, xs, dy } = fields.get(role);
3320
+ const x = col + 0.5 - half;
3321
+ if (x < box.x0 || x > box.x1) return 0;
3322
+ let k = 1;
3323
+ while (k < xs.length - 1 && xs[k] < x) k++;
3324
+ const t = (x - xs[k - 1]) / (xs[k] - xs[k - 1]);
3325
+ return dy[k - 1] + (dy[k] - dy[k - 1]) * t;
3326
+ };
3327
+ }
3328
+ function mouthKnots(union, opening) {
3329
+ const half = opening.canvasW / 2;
3330
+ const step = MOUTH_KNOT_PX;
3331
+ const xs = [union.x0];
3332
+ while (xs[xs.length - 1] + step < union.x1 - 0.3 * step)
3333
+ xs.push(xs[xs.length - 1] + step);
3334
+ xs.push(union.x1);
3335
+ const fixed = /* @__PURE__ */ new Set([union.x0, union.x1]);
3336
+ for (const x of [
3337
+ opening.x0 - half,
3338
+ opening.x1 - half,
3339
+ opening.x1 + 1 - half
3340
+ ]) {
3341
+ fixed.add(x);
3342
+ if (xs.includes(x)) continue;
3343
+ const near = xs.findIndex((v) => Math.abs(v - x) < 0.3 * step);
3344
+ if (near >= 0 && !fixed.has(xs[near])) xs[near] = x;
3345
+ else xs.push(x);
3346
+ }
3347
+ xs.sort((p, q) => p - q);
3348
+ for (const x of xs) {
3349
+ if (!Number.isInteger(x + half)) {
3350
+ throw new Error(
3351
+ `auto-rig: mouth knot ${x} is not an integer canvas boundary`
3352
+ );
3353
+ }
3354
+ }
3355
+ return xs;
3356
+ }
3357
+ function mouthMesh(box, knots) {
3358
+ const xs = [box.x0, ...knots.filter((k) => k > box.x0 && k < box.x1), box.x1];
3359
+ const rows = cellsFor(bh(box), MOUTH_KNOT_PX, 2, 40);
3360
+ const ys = Array.from(
3361
+ { length: rows + 1 },
3362
+ (_, r) => box.y1 - bh(box) * r / rows
3363
+ );
3364
+ return { mesh: columnMesh(box, { xs, ys }), xs };
3365
+ }
3366
+ function buildMouthRig(byRole) {
3367
+ const opening = mouthOpening(byRole);
3368
+ const union = unionBoxes(LIP_ROLES.map((r) => boxOfLayer(byRole.get(r))));
3369
+ return {
3370
+ opening,
3371
+ frame: mouthFrameOf(union),
3372
+ knots: mouthKnots(union, opening),
3373
+ union
3374
+ };
3375
+ }
3376
+ function mouthAnchor(byRole) {
3377
+ const box = (role) => boxOfLayer(byRole.get(role));
3378
+ if (isLipSet(byRole)) {
3379
+ const opening = mouthOpening(byRole);
3380
+ return {
3381
+ at: {
3382
+ x: cx(box("mouth_inner")),
3383
+ y: opening.at(opening.centre).seam
3384
+ },
3385
+ box: unionBoxes(LIP_ROLES.map(box))
3386
+ };
3387
+ }
3388
+ const m = box("mouth");
3389
+ return {
3390
+ at: { x: cx(m), y: cy(m) },
3391
+ box: byRole.has("mouth_open") ? unionBoxes([m, box("mouth_open")]) : m
3392
+ };
3393
+ }
3394
+
3162
3395
  // src/auto-rig/generate.ts
3163
3396
  import {
3164
3397
  IKI_FORMAT_VERSION as IKI_FORMAT_VERSION2,
@@ -3168,30 +3401,30 @@ import {
3168
3401
 
3169
3402
  // src/auto-rig/animations.ts
3170
3403
  import {
3171
- StandardParameter as P5
3404
+ StandardParameter as P6
3172
3405
  } from "@ikijs/format";
3173
3406
  function browsY(y) {
3174
3407
  return [
3175
- { parameter: P5.BrowLeftY, value: y, blend: "add" },
3176
- { parameter: P5.BrowRightY, value: y, blend: "add" }
3408
+ { parameter: P6.BrowLeftY, value: y, blend: "add" },
3409
+ { parameter: P6.BrowRightY, value: y, blend: "add" }
3177
3410
  ];
3178
3411
  }
3179
3412
  function browsTilt(innerDown) {
3180
3413
  return [
3181
- { parameter: P5.BrowLeftAngle, value: innerDown, blend: "add" },
3182
- { parameter: P5.BrowRightAngle, value: -innerDown, blend: "add" }
3414
+ { parameter: P6.BrowLeftAngle, value: innerDown, blend: "add" },
3415
+ { parameter: P6.BrowRightAngle, value: -innerDown, blend: "add" }
3183
3416
  ];
3184
3417
  }
3185
3418
  function cheek(value) {
3186
- return { parameter: P5.Cheek, value, blend: "add" };
3419
+ return { parameter: P6.Cheek, value, blend: "add" };
3187
3420
  }
3188
- var BROWS = [P5.BrowLeftY, P5.BrowRightY, P5.BrowLeftAngle, P5.BrowRightAngle];
3421
+ var BROWS = [P6.BrowLeftY, P6.BrowRightY, P6.BrowLeftAngle, P6.BrowRightAngle];
3189
3422
  var EXPRESSIONS = [
3190
3423
  {
3191
3424
  id: "smile",
3192
3425
  description: "Smiling and pleased: warm, friendly, content. For greetings, thanks, agreement and gentle happiness.",
3193
3426
  parameters: [
3194
- { parameter: P5.MouthForm, value: 1, blend: "add" },
3427
+ { parameter: P6.MouthForm, value: 1, blend: "add" },
3195
3428
  ...browsY(0.25),
3196
3429
  cheek(0.4)
3197
3430
  ]
@@ -3200,10 +3433,10 @@ var EXPRESSIONS = [
3200
3433
  id: "laugh",
3201
3434
  description: "Laughing, eyes shut and mouth open: delighted, amused. For jokes, playfulness and big joy.",
3202
3435
  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" },
3436
+ { parameter: P6.EyeOpenLeft, value: 0, blend: "multiply" },
3437
+ { parameter: P6.EyeOpenRight, value: 0, blend: "multiply" },
3438
+ { parameter: P6.MouthForm, value: 1, blend: "add" },
3439
+ { parameter: P6.MouthOpen, value: 0.8, blend: "overwrite" },
3207
3440
  ...browsY(0.4),
3208
3441
  cheek(0.65)
3209
3442
  ]
@@ -3215,7 +3448,7 @@ var EXPRESSIONS = [
3215
3448
  parameters: [
3216
3449
  ...browsY(-0.5),
3217
3450
  ...browsTilt(0.8),
3218
- { parameter: P5.MouthForm, value: -0.9, blend: "add" }
3451
+ { parameter: P6.MouthForm, value: -0.9, blend: "add" }
3219
3452
  ]
3220
3453
  },
3221
3454
  {
@@ -3225,7 +3458,7 @@ var EXPRESSIONS = [
3225
3458
  parameters: [
3226
3459
  ...browsY(0.35),
3227
3460
  ...browsTilt(-0.8),
3228
- { parameter: P5.MouthForm, value: -0.8, blend: "add" }
3461
+ { parameter: P6.MouthForm, value: -0.8, blend: "add" }
3229
3462
  ]
3230
3463
  },
3231
3464
  {
@@ -3233,18 +3466,18 @@ var EXPRESSIONS = [
3233
3466
  description: "Surprised, mouth dropped open: startled, amazed. For shock, sudden news or disbelief.",
3234
3467
  parameters: [
3235
3468
  ...browsY(0.8),
3236
- { parameter: P5.MouthOpen, value: 0.9, blend: "overwrite" }
3469
+ { parameter: P6.MouthOpen, value: 0.9, blend: "overwrite" }
3237
3470
  ]
3238
3471
  },
3239
3472
  {
3240
3473
  id: "shy",
3241
3474
  description: "Shy or embarrassed: bashful, flustered. For being praised, teased or caught off guard.",
3242
- needs: [P5.Cheek],
3475
+ needs: [P6.Cheek],
3243
3476
  parameters: [
3244
3477
  cheek(1),
3245
3478
  // Gaze down.
3246
- { parameter: P5.EyeballY, value: -0.65, blend: "add" },
3247
- { parameter: P5.MouthForm, value: 0.4, blend: "add" },
3479
+ { parameter: P6.EyeballY, value: -0.65, blend: "add" },
3480
+ { parameter: P6.MouthForm, value: 0.4, blend: "add" },
3248
3481
  ...browsTilt(-0.25)
3249
3482
  ]
3250
3483
  }
@@ -3264,7 +3497,7 @@ var HEAD_MOTIONS = {
3264
3497
  duration: 1,
3265
3498
  curves: [
3266
3499
  {
3267
- parameter: P5.AngleY,
3500
+ parameter: P6.AngleY,
3268
3501
  keys: [
3269
3502
  [0, 0],
3270
3503
  [0.3, -18],
@@ -3285,7 +3518,7 @@ var HEAD_MOTIONS = {
3285
3518
  fadeIn: 0.15,
3286
3519
  curves: [
3287
3520
  {
3288
- parameter: P5.AngleX,
3521
+ parameter: P6.AngleX,
3289
3522
  keys: [
3290
3523
  [0, 0],
3291
3524
  [0.2, -18],
@@ -3304,7 +3537,7 @@ var HEAD_MOTIONS = {
3304
3537
  duration: 1.4,
3305
3538
  curves: [
3306
3539
  {
3307
- parameter: P5.AngleZ,
3540
+ parameter: P6.AngleZ,
3308
3541
  keys: [
3309
3542
  [0, 0],
3310
3543
  [0.35, 20],
@@ -3325,7 +3558,7 @@ var WAVE = {
3325
3558
  fadeOut: 0.15,
3326
3559
  curves: [
3327
3560
  {
3328
- parameter: P5.ArmPoseRight,
3561
+ parameter: P6.ArmPoseRight,
3329
3562
  interpolation: "linear",
3330
3563
  keys: [
3331
3564
  [0, 0],
@@ -3335,7 +3568,7 @@ var WAVE = {
3335
3568
  ]
3336
3569
  },
3337
3570
  {
3338
- parameter: P5.ArmRight,
3571
+ parameter: P6.ArmRight,
3339
3572
  keys: [
3340
3573
  [0, 0],
3341
3574
  [0.4, 10],
@@ -3344,7 +3577,7 @@ var WAVE = {
3344
3577
  ]
3345
3578
  },
3346
3579
  {
3347
- parameter: P5.ArmPoseAngleRight,
3580
+ parameter: P6.ArmPoseAngleRight,
3348
3581
  keys: [
3349
3582
  [0, 0],
3350
3583
  [0.5, 0],
@@ -3360,7 +3593,7 @@ var WAVE = {
3360
3593
  ]
3361
3594
  };
3362
3595
  function defaultMotions(declared) {
3363
- const waves = [P5.ArmRight, P5.ArmPoseRight, P5.ArmPoseAngleRight].every(
3596
+ const waves = [P6.ArmRight, P6.ArmPoseRight, P6.ArmPoseAngleRight].every(
3364
3597
  (id) => declared.has(id)
3365
3598
  );
3366
3599
  return waves ? { ...HEAD_MOTIONS, ...WAVE } : HEAD_MOTIONS;
@@ -3598,181 +3831,6 @@ function checkStrandEdges(strand, irises, hair) {
3598
3831
  // src/auto-rig/context.ts
3599
3832
  import { StandardParameter as P7 } from "@ikijs/format";
3600
3833
 
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
3834
  // src/auto-rig/head.ts
3777
3835
  function outerEdge(runs, axisX, side) {
3778
3836
  if (runs.length === 0) return void 0;
@@ -6184,6 +6242,7 @@ export {
6184
6242
  DeletePhysicsRig,
6185
6243
  EditorDocument,
6186
6244
  HEAD_BAND,
6245
+ LIP_ROLES,
6187
6246
  LayerGeometryError,
6188
6247
  SPECK_CORE_FRACTION,
6189
6248
  SetDeformerBindings,
@@ -6205,6 +6264,7 @@ export {
6205
6264
  UV_INSET_PX,
6206
6265
  armGeometry,
6207
6266
  captureBindingEndpoint,
6267
+ columnRuns,
6208
6268
  computeGridOffsets,
6209
6269
  createDefaultMatrixDeformer,
6210
6270
  createDefaultPart,
@@ -6219,6 +6279,9 @@ export {
6219
6279
  headHalfOf,
6220
6280
  interpolateGridOffsets,
6221
6281
  isSpeckCore,
6282
+ mouthFold,
6283
+ mouthOpening,
6284
+ mouthRestShift,
6222
6285
  packAtlas,
6223
6286
  parseLayerRoles,
6224
6287
  partIdsOfRole,