@ikijs/editor 0.24.1 → 0.25.1

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
@@ -1476,8 +1476,8 @@ function foregroundSpan(mask, width, height, rowLo, rowHi) {
1476
1476
  }
1477
1477
  return { left, right };
1478
1478
  }
1479
- function headHalfOf(span) {
1480
- return (span.right - span.left + 1) / 2;
1479
+ function headHalfOf(span2) {
1480
+ return (span2.right - span2.left + 1) / 2;
1481
1481
  }
1482
1482
  var DARK_SHARE = 0.55;
1483
1483
  function jawRowsOf(rgba, canvasW, bbox, rowLeft, rowRight) {
@@ -1487,9 +1487,9 @@ function jawRowsOf(rgba, canvasW, bbox, rowLeft, rowRight) {
1487
1487
  let widest = bbox.y;
1488
1488
  let widestSpan = -1;
1489
1489
  for (let y = bbox.y; y < bbox.y + bbox.h; y++) {
1490
- const span = rowRight[y] - rowLeft[y];
1491
- if (span >= widestSpan) {
1492
- widestSpan = span;
1490
+ const span2 = rowRight[y] - rowLeft[y];
1491
+ if (span2 >= widestSpan) {
1492
+ widestSpan = span2;
1493
1493
  widest = y;
1494
1494
  }
1495
1495
  for (let x = bbox.x; x < bbox.x + bbox.w; x++) {
@@ -1658,14 +1658,14 @@ function createLayerSetMeasurer(canvas) {
1658
1658
  }
1659
1659
  function finish() {
1660
1660
  const eyeRow = eyeBandRowOf(layerInputs);
1661
- const span = foregroundSpan(
1661
+ const span2 = foregroundSpan(
1662
1662
  opaque,
1663
1663
  canvasW,
1664
1664
  canvasH,
1665
1665
  eyeRow - HEAD_BAND,
1666
1666
  eyeRow + HEAD_BAND
1667
1667
  );
1668
- const headHalfWidth = span.right > span.left ? headHalfOf(span) : void 0;
1668
+ const headHalfWidth = span2.right > span2.left ? headHalfOf(span2) : void 0;
1669
1669
  const headHalfWidthApplied = headHalfWidth !== void 0 && headHalfWidth > facePlateHalfOf(layerInputs);
1670
1670
  let headEdges;
1671
1671
  if (headHalfWidthApplied) {
@@ -1693,9 +1693,9 @@ function createLayerSetMeasurer(canvas) {
1693
1693
  const centre = iris.bbox.x + iris.bbox.w / 2;
1694
1694
  const otherX = other.bbox.x + other.bbox.w / 2 - half;
1695
1695
  const c = side < 0 ? Math.ceil(centre) - 1 : Math.floor(centre);
1696
- const span2 = rowSpansByRole.find((s) => s.role === iris.role);
1697
- if (span2.rowRight[r] < span2.rowLeft[r]) return void 0;
1698
- const minRun = STRAND_MIN_RUN_FRACTION * (span2.rowRight[r] + 1 - span2.rowLeft[r]);
1696
+ const span3 = rowSpansByRole.find((s) => s.role === iris.role);
1697
+ if (span3.rowRight[r] < span3.rowLeft[r]) return void 0;
1698
+ const minRun = STRAND_MIN_RUN_FRACTION * (span3.rowRight[r] + 1 - span3.rowLeft[r]);
1699
1699
  const strand = new Uint8Array(canvasW);
1700
1700
  const runs = r >= hairTop && r < hairTop + hairRuns.length ? hairRuns[r - hairTop] : [];
1701
1701
  for (let k = 0; k < runs.length; k += 2) {
@@ -1720,8 +1720,8 @@ function createLayerSetMeasurer(canvas) {
1720
1720
  while (strandAt(x)) x += side;
1721
1721
  return {
1722
1722
  y: canvasH / 2 - (r + 0.5),
1723
- irisOuter: (side < 0 ? span2.rowLeft[r] : span2.rowRight[r] + 1) - half,
1724
- irisInner: (side < 0 ? span2.rowRight[r] + 1 : span2.rowLeft[r]) - half,
1723
+ irisOuter: (side < 0 ? span3.rowLeft[r] : span3.rowRight[r] + 1) - half,
1724
+ irisInner: (side < 0 ? span3.rowRight[r] + 1 : span3.rowLeft[r]) - half,
1725
1725
  runOuter: outerBoundary(x - side),
1726
1726
  runFace
1727
1727
  };
@@ -2125,6 +2125,12 @@ var DEFAULT_TURN_TARGETS = Object.freeze({
2125
2125
  farEyeRatio: round2(TURN.eyeFarScale / TURN.eyeNearScale),
2126
2126
  silhouetteRatio: 1
2127
2127
  });
2128
+ var PatchError = class extends Error {
2129
+ constructor(message) {
2130
+ super(message);
2131
+ this.name = "PatchError";
2132
+ }
2133
+ };
2128
2134
 
2129
2135
  // src/auto-rig/arms.ts
2130
2136
  import {
@@ -2199,15 +2205,16 @@ function gridMesh(cols, rows) {
2199
2205
  return { vertices, uvs, indices };
2200
2206
  }
2201
2207
  function columnMesh(b, grid) {
2202
- const { xs } = grid;
2203
- const rows = grid.ys.length - 1;
2208
+ const { xs, ys } = grid;
2209
+ const rows = ys.length - 1;
2204
2210
  const vertices = [];
2205
2211
  const uvs = [];
2206
2212
  for (let r = 0; r <= rows; r++) {
2213
+ const v = (ys[0] - ys[r]) / (ys[0] - ys[rows]);
2207
2214
  for (const x of xs) {
2208
2215
  const u = (x - b.x0) / bw(b);
2209
- vertices.push(roundTo(u - 0.5, 1e-5), roundTo(0.5 - r / rows, 1e-5));
2210
- uvs.push(roundTo(u, 1e-5), roundTo(r / rows, 1e-5));
2216
+ vertices.push(roundTo(u - 0.5, 1e-5), roundTo(0.5 - v, 1e-5));
2217
+ uvs.push(roundTo(u, 1e-5), roundTo(v, 1e-5));
2211
2218
  }
2212
2219
  }
2213
2220
  const n = xs.length;
@@ -2756,7 +2763,7 @@ var ARM_IDS = {
2756
2763
  };
2757
2764
  function endPivot(layer, end) {
2758
2765
  const { bbox, canvasW, canvasH, rowRuns } = layer;
2759
- const span = runSpan(layer);
2766
+ const span2 = runSpan(layer);
2760
2767
  const from = (k) => end === "top" ? k : bbox.h - 1 - k;
2761
2768
  const painted = rowRuns?.flatMap((runs, k) => runs.length > 0 ? [k] : []) ?? [];
2762
2769
  const firstK = end === "top" ? painted[0] ?? 0 : painted.at(-1) ?? bbox.h - 1;
@@ -2764,13 +2771,13 @@ function endPivot(layer, end) {
2764
2771
  const hi = Math.max(lo, Math.floor(CAP_ROWS[1] * bbox.h));
2765
2772
  const widths = [];
2766
2773
  for (let k = lo; k <= hi; k++) {
2767
- const [s2, e2] = span(from(k));
2774
+ const [s2, e2] = span2(from(k));
2768
2775
  widths.push(e2 - s2);
2769
2776
  }
2770
2777
  widths.sort((p, q) => p - q);
2771
2778
  const radius = widths[widths.length >> 1] / 2;
2772
2779
  const ks = end === "top" ? firstK + radius : firstK - radius;
2773
- const [s, e] = span(Math.round(ks));
2780
+ const [s, e] = span2(Math.round(ks));
2774
2781
  return {
2775
2782
  point: {
2776
2783
  x: roundTo((s + e) / 2 - canvasW / 2, 0.01),
@@ -2795,9 +2802,9 @@ function runSpan(layer) {
2795
2802
  function armGeometry(layer, bodyAxisX) {
2796
2803
  const { bbox, canvasW, canvasH, rowRuns } = layer;
2797
2804
  const b = boxOfLayer(layer);
2798
- const span = runSpan(layer);
2805
+ const span2 = runSpan(layer);
2799
2806
  const centreX = (k) => {
2800
- const [s2, e2] = span(k);
2807
+ const [s2, e2] = span2(k);
2801
2808
  return (s2 + e2) / 2 - canvasW / 2;
2802
2809
  };
2803
2810
  const rowY = (k) => canvasH / 2 - (bbox.y + k + 0.5);
@@ -2811,7 +2818,7 @@ function armGeometry(layer, bodyAxisX) {
2811
2818
  );
2812
2819
  }
2813
2820
  const elbow = { x: roundTo(centreX(ke), 0.01), y: roundTo(rowY(ke), 0.01) };
2814
- const [s, e] = span(ke);
2821
+ const [s, e] = span2(ke);
2815
2822
  const capRadius = Math.min(
2816
2823
  (e - s) / 2 + 1,
2817
2824
  elbow.x - b.x0,
@@ -2821,7 +2828,7 @@ function armGeometry(layer, bodyAxisX) {
2821
2828
  const inside = (x, y) => {
2822
2829
  const k = Math.floor(y) - bbox.y;
2823
2830
  if (k < 0 || k >= bbox.h) return false;
2824
- const [rs, re] = span(k);
2831
+ const [rs, re] = span2(k);
2825
2832
  return rs < x && x < re;
2826
2833
  };
2827
2834
  const capRays = Array.from({ length: CAP_SEGMENTS + 1 }, (_, i) => {
@@ -3031,14 +3038,25 @@ function forearmPoseDeformer(role, arm) {
3031
3038
 
3032
3039
  // src/auto-rig/mouth.ts
3033
3040
  import {
3034
- StandardParameter as P5
3041
+ StandardParameter as P6
3035
3042
  } from "@ikijs/format";
3036
3043
 
3037
3044
  // src/auto-rig/roles.ts
3045
+ import { StandardParameter as P5 } from "@ikijs/format";
3038
3046
  var LIP_ROLES = ["mouth_inner", "lip_lower", "lip_upper"];
3039
3047
  var isLipRole = (role) => LIP_ROLES.includes(role);
3040
3048
  var LIP_INSIDE_ROLES = ["mouth_tongue", "mouth_teeth"];
3041
3049
  var isLipInsideRole = (role) => LIP_INSIDE_ROLES.includes(role);
3050
+ var SHUT_BY = new Map([
3051
+ ["eye_L", P5.EyeOpenLeft],
3052
+ ["lash_lower_L", P5.EyeOpenLeft],
3053
+ ["eye_R", P5.EyeOpenRight],
3054
+ ["lash_lower_R", P5.EyeOpenRight],
3055
+ ...[...LIP_ROLES, ...LIP_INSIDE_ROLES].map((id) => [
3056
+ id,
3057
+ P5.MouthOpen
3058
+ ])
3059
+ ]);
3042
3060
  var ROLE_TABLE = [
3043
3061
  { role: "hair_back", family: "hair_back" },
3044
3062
  { role: "body", family: "body" },
@@ -3383,7 +3401,7 @@ function columnOfKnot(knot, opening) {
3383
3401
  function mouthFoldWarp(role, mesh, xs, box, opening) {
3384
3402
  const fold = mouthFold(role, opening);
3385
3403
  return localWarp(
3386
- P5.MouthOpen,
3404
+ P6.MouthOpen,
3387
3405
  mesh,
3388
3406
  box,
3389
3407
  [0, 1],
@@ -3392,13 +3410,13 @@ function mouthFoldWarp(role, mesh, xs, box, opening) {
3392
3410
  }
3393
3411
  function mouthRestShift(byRole) {
3394
3412
  const rig = buildMouthRig(byRole);
3395
- const fields = /* @__PURE__ */ new Map();
3413
+ const fields2 = /* @__PURE__ */ new Map();
3396
3414
  for (const role of ["lip_upper", "lip_lower"]) {
3397
3415
  const box = boxOfLayer(byRole.get(role));
3398
3416
  const { mesh, xs } = mouthMesh(box, rig.knots);
3399
3417
  const warp = mouthFoldWarp(role, mesh, xs, box, rig.opening);
3400
3418
  const offsets = warp.keyforms[0].offsets;
3401
- fields.set(role, {
3419
+ fields2.set(role, {
3402
3420
  box,
3403
3421
  xs,
3404
3422
  rows: offsets.length / 2 / xs.length - 1,
@@ -3407,7 +3425,7 @@ function mouthRestShift(byRole) {
3407
3425
  }
3408
3426
  const { canvasW, canvasH } = byRole.get("lip_upper");
3409
3427
  return (role, col, row) => {
3410
- const { box, xs, rows, dy } = fields.get(role);
3428
+ const { box, xs, rows, dy } = fields2.get(role);
3411
3429
  const x = col + 0.5 - canvasW / 2;
3412
3430
  const y = canvasH / 2 - (row + 0.5);
3413
3431
  if (x < box.x0 || x > box.x1 || y < box.y0 || y > box.y1) return void 0;
@@ -3498,36 +3516,36 @@ function mouthAnchor(byRole) {
3498
3516
  // src/auto-rig/generate.ts
3499
3517
  import {
3500
3518
  IKI_FORMAT_VERSION as IKI_FORMAT_VERSION2,
3501
- StandardParameter as P8,
3519
+ StandardParameter as P10,
3502
3520
  parseIkiModel as parseIkiModel3
3503
3521
  } from "@ikijs/format";
3504
3522
 
3505
3523
  // src/auto-rig/animations.ts
3506
3524
  import {
3507
- StandardParameter as P6
3525
+ StandardParameter as P7
3508
3526
  } from "@ikijs/format";
3509
3527
  function browsY(y) {
3510
3528
  return [
3511
- { parameter: P6.BrowLeftY, value: y, blend: "add" },
3512
- { parameter: P6.BrowRightY, value: y, blend: "add" }
3529
+ { parameter: P7.BrowLeftY, value: y, blend: "add" },
3530
+ { parameter: P7.BrowRightY, value: y, blend: "add" }
3513
3531
  ];
3514
3532
  }
3515
3533
  function browsTilt(innerDown) {
3516
3534
  return [
3517
- { parameter: P6.BrowLeftAngle, value: innerDown, blend: "add" },
3518
- { parameter: P6.BrowRightAngle, value: -innerDown, blend: "add" }
3535
+ { parameter: P7.BrowLeftAngle, value: innerDown, blend: "add" },
3536
+ { parameter: P7.BrowRightAngle, value: -innerDown, blend: "add" }
3519
3537
  ];
3520
3538
  }
3521
3539
  function cheek(value) {
3522
- return { parameter: P6.Cheek, value, blend: "add" };
3540
+ return { parameter: P7.Cheek, value, blend: "add" };
3523
3541
  }
3524
- var BROWS = [P6.BrowLeftY, P6.BrowRightY, P6.BrowLeftAngle, P6.BrowRightAngle];
3542
+ var BROWS = [P7.BrowLeftY, P7.BrowRightY, P7.BrowLeftAngle, P7.BrowRightAngle];
3525
3543
  var EXPRESSIONS = [
3526
3544
  {
3527
3545
  id: "smile",
3528
3546
  description: "Smiling and pleased: warm, friendly, content. For greetings, thanks, agreement and gentle happiness.",
3529
3547
  parameters: [
3530
- { parameter: P6.MouthForm, value: 1, blend: "add" },
3548
+ { parameter: P7.MouthForm, value: 1, blend: "add" },
3531
3549
  ...browsY(0.25),
3532
3550
  cheek(0.4)
3533
3551
  ]
@@ -3536,10 +3554,10 @@ var EXPRESSIONS = [
3536
3554
  id: "laugh",
3537
3555
  description: "Laughing, eyes shut and mouth open: delighted, amused. For jokes, playfulness and big joy.",
3538
3556
  parameters: [
3539
- { parameter: P6.EyeOpenLeft, value: 0, blend: "multiply" },
3540
- { parameter: P6.EyeOpenRight, value: 0, blend: "multiply" },
3541
- { parameter: P6.MouthForm, value: 1, blend: "add" },
3542
- { parameter: P6.MouthOpen, value: 0.8, blend: "overwrite" },
3557
+ { parameter: P7.EyeOpenLeft, value: 0, blend: "multiply" },
3558
+ { parameter: P7.EyeOpenRight, value: 0, blend: "multiply" },
3559
+ { parameter: P7.MouthForm, value: 1, blend: "add" },
3560
+ { parameter: P7.MouthOpen, value: 0.8, blend: "overwrite" },
3543
3561
  ...browsY(0.4),
3544
3562
  cheek(0.65)
3545
3563
  ]
@@ -3551,7 +3569,7 @@ var EXPRESSIONS = [
3551
3569
  parameters: [
3552
3570
  ...browsY(-0.5),
3553
3571
  ...browsTilt(0.8),
3554
- { parameter: P6.MouthForm, value: -0.9, blend: "add" }
3572
+ { parameter: P7.MouthForm, value: -0.9, blend: "add" }
3555
3573
  ]
3556
3574
  },
3557
3575
  {
@@ -3561,7 +3579,7 @@ var EXPRESSIONS = [
3561
3579
  parameters: [
3562
3580
  ...browsY(0.35),
3563
3581
  ...browsTilt(-0.8),
3564
- { parameter: P6.MouthForm, value: -0.8, blend: "add" }
3582
+ { parameter: P7.MouthForm, value: -0.8, blend: "add" }
3565
3583
  ]
3566
3584
  },
3567
3585
  {
@@ -3569,18 +3587,18 @@ var EXPRESSIONS = [
3569
3587
  description: "Surprised, mouth dropped open: startled, amazed. For shock, sudden news or disbelief.",
3570
3588
  parameters: [
3571
3589
  ...browsY(0.8),
3572
- { parameter: P6.MouthOpen, value: 0.9, blend: "overwrite" }
3590
+ { parameter: P7.MouthOpen, value: 0.9, blend: "overwrite" }
3573
3591
  ]
3574
3592
  },
3575
3593
  {
3576
3594
  id: "shy",
3577
3595
  description: "Shy or embarrassed: bashful, flustered. For being praised, teased or caught off guard.",
3578
- needs: [P6.Cheek],
3596
+ needs: [P7.Cheek],
3579
3597
  parameters: [
3580
3598
  cheek(1),
3581
3599
  // Gaze down.
3582
- { parameter: P6.EyeballY, value: -0.65, blend: "add" },
3583
- { parameter: P6.MouthForm, value: 0.4, blend: "add" },
3600
+ { parameter: P7.EyeballY, value: -0.65, blend: "add" },
3601
+ { parameter: P7.MouthForm, value: 0.4, blend: "add" },
3584
3602
  ...browsTilt(-0.25)
3585
3603
  ]
3586
3604
  }
@@ -3600,7 +3618,7 @@ var HEAD_MOTIONS = {
3600
3618
  duration: 1,
3601
3619
  curves: [
3602
3620
  {
3603
- parameter: P6.AngleY,
3621
+ parameter: P7.AngleY,
3604
3622
  keys: [
3605
3623
  [0, 0],
3606
3624
  [0.3, -18],
@@ -3621,7 +3639,7 @@ var HEAD_MOTIONS = {
3621
3639
  fadeIn: 0.15,
3622
3640
  curves: [
3623
3641
  {
3624
- parameter: P6.AngleX,
3642
+ parameter: P7.AngleX,
3625
3643
  keys: [
3626
3644
  [0, 0],
3627
3645
  [0.2, -18],
@@ -3640,7 +3658,7 @@ var HEAD_MOTIONS = {
3640
3658
  duration: 1.4,
3641
3659
  curves: [
3642
3660
  {
3643
- parameter: P6.AngleZ,
3661
+ parameter: P7.AngleZ,
3644
3662
  keys: [
3645
3663
  [0, 0],
3646
3664
  [0.35, 20],
@@ -3661,7 +3679,7 @@ var WAVE = {
3661
3679
  fadeOut: 0.15,
3662
3680
  curves: [
3663
3681
  {
3664
- parameter: P6.ArmPoseRight,
3682
+ parameter: P7.ArmPoseRight,
3665
3683
  interpolation: "linear",
3666
3684
  keys: [
3667
3685
  [0, 0],
@@ -3671,7 +3689,7 @@ var WAVE = {
3671
3689
  ]
3672
3690
  },
3673
3691
  {
3674
- parameter: P6.ArmRight,
3692
+ parameter: P7.ArmRight,
3675
3693
  keys: [
3676
3694
  [0, 0],
3677
3695
  [0.4, 10],
@@ -3680,7 +3698,7 @@ var WAVE = {
3680
3698
  ]
3681
3699
  },
3682
3700
  {
3683
- parameter: P6.ArmPoseAngleRight,
3701
+ parameter: P7.ArmPoseAngleRight,
3684
3702
  keys: [
3685
3703
  [0, 0],
3686
3704
  [0.5, 0],
@@ -3696,7 +3714,7 @@ var WAVE = {
3696
3714
  ]
3697
3715
  };
3698
3716
  function defaultMotions(declared) {
3699
- const waves = [P6.ArmRight, P6.ArmPoseRight, P6.ArmPoseAngleRight].every(
3717
+ const waves = [P7.ArmRight, P7.ArmPoseRight, P7.ArmPoseAngleRight].every(
3700
3718
  (id) => declared.has(id)
3701
3719
  );
3702
3720
  return waves ? { ...HEAD_MOTIONS, ...WAVE } : HEAD_MOTIONS;
@@ -3932,7 +3950,7 @@ function checkStrandEdges(strand, irises, hair) {
3932
3950
  }
3933
3951
 
3934
3952
  // src/auto-rig/context.ts
3935
- import { StandardParameter as P7 } from "@ikijs/format";
3953
+ import { StandardParameter as P8 } from "@ikijs/format";
3936
3954
 
3937
3955
  // src/auto-rig/head.ts
3938
3956
  function outerEdge(runs, axisX, side) {
@@ -4041,14 +4059,14 @@ function buildHeadFrame(layers, opts) {
4041
4059
  const rawRows = raw;
4042
4060
  const earless = (chin) => {
4043
4061
  const rowOf = (y) => Math.round(canvasH / 2 - y - faceLayer.bbox.y - 0.5);
4044
- const span = Math.max(1, eyeY - chin);
4062
+ const span2 = Math.max(1, eyeY - chin);
4045
4063
  const found = findEars(
4046
4064
  rawRows,
4047
4065
  wMax,
4048
4066
  Math.round(0.2 * rawRows.length),
4049
4067
  Math.min(rawRows.length - 1, rowOf(chin)),
4050
- Math.round(0.1 * span),
4051
- Math.round(0.8 * span)
4068
+ Math.round(0.1 * span2),
4069
+ Math.round(0.8 * span2)
4052
4070
  );
4053
4071
  if (found === void 0) return profile;
4054
4072
  const { top, bottom } = found;
@@ -4082,16 +4100,16 @@ function buildHeadFrame(layers, opts) {
4082
4100
  }
4083
4101
  }
4084
4102
  if (neckRow >= 0 && n - neckRow >= 0.12 * n) {
4085
- let span = 0;
4103
+ let span2 = 0;
4086
4104
  let waist = Infinity;
4087
4105
  for (let r = neckRow; r < n; r++) {
4088
- span = Math.max(span, profile[r]);
4106
+ span2 = Math.max(span2, profile[r]);
4089
4107
  if (r < n - tail) waist = Math.min(waist, profile[r]);
4090
4108
  }
4091
4109
  neck = {
4092
4110
  topY: rowY(neckRow),
4093
4111
  half: profile[neckRow],
4094
- span,
4112
+ span: span2,
4095
4113
  waist: Math.min(waist, profile[neckRow])
4096
4114
  };
4097
4115
  }
@@ -4291,22 +4309,22 @@ function earField(m) {
4291
4309
  const dy = face(x, y, 0, ay)[1];
4292
4310
  const d = Math.abs(x - f.axisX);
4293
4311
  const attach = ears.attachAt(y);
4294
- const span = Math.max(1, ears.outer - attach);
4312
+ const span2 = Math.max(1, ears.outer - attach);
4295
4313
  if (sideOf(m, x, s) === "far") {
4296
4314
  const rim2 = TURN.earFar * m.face;
4297
4315
  const root2 = earRoot(m, face, y, s);
4298
4316
  const lead = Math.max(
4299
4317
  0,
4300
- Math.min((1 - m.earFarScale) * span, root2 - rim2)
4318
+ Math.min((1 - m.earFarScale) * span2, root2 - rim2)
4301
4319
  );
4302
- let along = rim2 + lead * (ears.outer - d) / span;
4320
+ let along = rim2 + lead * (ears.outer - d) / span2;
4303
4321
  if (d < attach) along = Math.min(along, s * face(x, y, 30 * s, 0)[0]);
4304
4322
  return [s * a * along, dy];
4305
4323
  }
4306
4324
  if (d <= attach) return [face(x, y, ax, 0)[0], dy];
4307
4325
  const root = face(f.axisX + Math.sign(x - f.axisX) * attach, y, ax, 0)[0];
4308
4326
  const rim = s * a * TURN.earNear * m.face;
4309
- const t = Math.min(1, (d - attach) / span);
4327
+ const t = Math.min(1, (d - attach) / span2);
4310
4328
  return [root + (rim - root) * t, dy];
4311
4329
  };
4312
4330
  }
@@ -4470,7 +4488,17 @@ function hairFrontField(m) {
4470
4488
  );
4471
4489
  return core + (m.hairOuter - core) * out;
4472
4490
  };
4473
- const ride = outerRide(m, inner, coreAt, followAt, eyeY + 1.5 * eyeH);
4491
+ const ride = outerRide(
4492
+ m,
4493
+ inner,
4494
+ coreAt,
4495
+ followAt,
4496
+ eyeY + 1.5 * eyeH,
4497
+ eyeY + eyeH / 2,
4498
+ Math.max(
4499
+ ...[m.boxes.iris_L, m.boxes.iris_R].map((b) => b?.y1 ?? -Infinity)
4500
+ )
4501
+ );
4474
4502
  const followOf = (x, y, s) => followAt(x, y, s) + (ride === void 0 ? 0 : ride.lift(x, y, s));
4475
4503
  const drift = Math.abs(m.hairBack);
4476
4504
  const crown = crownRide(m, cap, (x, y, s) => followOf(x, y, s) + drift);
@@ -4497,9 +4525,12 @@ function crownBlend(m, y) {
4497
4525
  }
4498
4526
  function gridRow(ys, y, snap = false) {
4499
4527
  const n = ys.length;
4500
- let v = clamp((ys[0] - y) / (ys[0] - ys[n - 1]) * (n - 1), 0, n - 1);
4528
+ const yc = clamp(y, ys[n - 1], ys[0]);
4529
+ let r = 0;
4530
+ while (r < n - 2 && ys[r + 1] >= yc) r++;
4531
+ let v = r + (ys[r] - yc) / (ys[r] - ys[r + 1]);
4501
4532
  if (snap && Math.abs(v - Math.round(v)) < 1e-3) v = Math.round(v);
4502
- const r = Math.min(n - 2, Math.floor(v));
4533
+ r = Math.min(n - 2, Math.floor(v));
4503
4534
  return { r, t: v - r };
4504
4535
  }
4505
4536
  function gridCol(xs, x) {
@@ -4589,7 +4620,7 @@ function paints(runs, px) {
4589
4620
  }
4590
4621
  return false;
4591
4622
  }
4592
- function outerRide(m, inner, coreAt, followAt, capLimit) {
4623
+ function outerRide(m, inner, coreAt, followAt, capLimit, eyeTop, irisTop) {
4593
4624
  const f = m.frame;
4594
4625
  const grid = m.hairFrontGrid;
4595
4626
  if (grid === void 0) return void 0;
@@ -4776,10 +4807,25 @@ function outerRide(m, inner, coreAt, followAt, capLimit) {
4776
4807
  }
4777
4808
  for (let j = 0; j < 2; j++) {
4778
4809
  for (let r = n - 2; r >= 0; r--) {
4779
- if (wr[r] < 1)
4810
+ if (r <= last || wr[r] < 1)
4780
4811
  blend[r * 2 + j] = Math.max(blend[r * 2 + j], blend[(r + 1) * 2 + j]);
4781
4812
  }
4782
4813
  }
4814
+ let zero = last + 1;
4815
+ while (zero < n && ys[zero] > eyeTop) zero++;
4816
+ while (zero > last + 1 && zero < n && ys[zero] < irisTop - 0.5) zero--;
4817
+ const endY = zero < n ? ys[zero] : Math.min(Math.max(eyeTop, irisTop - 0.5), ys[n - 1] - 1);
4818
+ for (let j = 0; j < 2 && last < n - 1; j++) {
4819
+ for (let r = 1; r < zero; r++) {
4820
+ for (let q = 0; q < r && q <= last; q++) {
4821
+ if (wr[q] < 1) continue;
4822
+ blend[r * 2 + j] = Math.max(
4823
+ blend[r * 2 + j],
4824
+ blend[q * 2 + j] * (ys[r] - endY) / (ys[q] - endY)
4825
+ );
4826
+ }
4827
+ }
4828
+ }
4783
4829
  const cutRow = (r, j, x) => r > last ? 0 : along(cuts.subarray((r * 2 + j) * nc, (r * 2 + j + 1) * nc), x);
4784
4830
  return {
4785
4831
  lift: liftAt,
@@ -5062,7 +5108,7 @@ function renderedLander(turn, parts, grids, from = /* @__PURE__ */ new Map()) {
5062
5108
  const family = roleSpec(role).family;
5063
5109
  const part = parts.find((p) => p.id === role);
5064
5110
  const g = grids.get(family);
5065
- const warp = part?.warps?.find((w) => w.parameter === P7.AngleX);
5111
+ const warp = part?.warps?.find((w) => w.parameter === P8.AngleX);
5066
5112
  if (part === void 0 || part.mesh === void 0 || !g && !warp) {
5067
5113
  return x + field(role)(x, y, ax, 0)[0];
5068
5114
  }
@@ -5201,11 +5247,11 @@ function buildFaceMesh(frame, b, head, neckCells) {
5201
5247
  const indices = [];
5202
5248
  const neck = frame.neck;
5203
5249
  if (neck !== void 0) {
5204
- const span = neck.span + pad;
5205
- const nc = cellsFor(2 * span, neckCells.px, neckCells.min, neckCells.max);
5250
+ const span2 = neck.span + pad;
5251
+ const nc = cellsFor(2 * span2, neckCells.px, neckCells.min, neckCells.max);
5206
5252
  const xs2 = columns(
5207
- Math.max(b.x0, frame.axisX - span),
5208
- Math.min(b.x1, frame.axisX + span),
5253
+ Math.max(b.x0, frame.axisX - span2),
5254
+ Math.min(b.x1, frame.axisX + span2),
5209
5255
  nc
5210
5256
  );
5211
5257
  const sampleBelow = (x) => Math.max(
@@ -5341,6 +5387,11 @@ function buildFaceMesh(frame, b, head, neckCells) {
5341
5387
  return { mesh: { vertices, uvs, indices }, region, headStart };
5342
5388
  }
5343
5389
 
5390
+ // src/auto-rig/patches.ts
5391
+ import {
5392
+ StandardParameter as P9
5393
+ } from "@ikijs/format";
5394
+
5344
5395
  // src/auto-rig/solve.ts
5345
5396
  var RANGES = {
5346
5397
  // [lo, hi, lo exclusive]
@@ -5771,6 +5822,235 @@ function buildTurn(frame, landmarks, q) {
5771
5822
  };
5772
5823
  }
5773
5824
 
5825
+ // src/auto-rig/patches.ts
5826
+ var OP_KEYS = ["target", "at", "region", "by", "falloff", "why"];
5827
+ var REGION_KEYS = ["role", "x", "y"];
5828
+ var BY_KEYS = ["dx", "dy"];
5829
+ var ROLLS = /* @__PURE__ */ new Set([P9.AngleZ, P9.BodyAngleZ]);
5830
+ var REST_IS_ART = "rest is the drawn art, which no patch changes (change it with the layout or by regenerating the layer)";
5831
+ function applyPatches(patches, ctx) {
5832
+ if (!Array.isArray(patches)) {
5833
+ throw new PatchError(
5834
+ `patches must be an array, got ${patches === null ? "null" : typeof patches}`
5835
+ );
5836
+ }
5837
+ const sums = /* @__PURE__ */ new Map();
5838
+ const reports = patches.map((op, i) => replay(op, i, ctx, sums));
5839
+ for (const [offsets, s] of sums) {
5840
+ s.terms.forEach((t, k) => {
5841
+ if (t.length === 0) return;
5842
+ const d = t.sort((a, b) => a - b).reduce((a, b) => a + b, 0);
5843
+ offsets[k] = roundTo(offsets[k] + d / s.scale[k % 2], s.step);
5844
+ });
5845
+ }
5846
+ return reports;
5847
+ }
5848
+ function replay(op, i, ctx, sums) {
5849
+ const name = `patches[${i}]`;
5850
+ const o = fields(name, op, OP_KEYS);
5851
+ const target = text(`${name}.target`, o.target);
5852
+ const at = numbers(`${name}.at`, o.at);
5853
+ const region = fields(`${name}.region`, o.region, REGION_KEYS);
5854
+ const role = text(`${name}.region.role`, region.role);
5855
+ const x = span(`${name}.region.x`, region.x);
5856
+ const y = span(`${name}.region.y`, region.y);
5857
+ const by = fields(`${name}.by`, o.by, BY_KEYS);
5858
+ const dx = finite2(`${name}.by.dx`, by.dx);
5859
+ const dy = finite2(`${name}.by.dy`, by.dy);
5860
+ const falloff = o.falloff === void 0 ? 0 : finite2(`${name}.falloff`, o.falloff, 0);
5861
+ if (o.why !== void 0) text(`${name}.why`, o.why);
5862
+ const box = ctx.boxOf(role);
5863
+ if (box === void 0) {
5864
+ const roles = [...new Set(ctx.parts.map((p) => roleOfPart(p.id)))].filter(
5865
+ (r) => ctx.boxOf(r) !== void 0
5866
+ );
5867
+ throw new PatchError(
5868
+ `${name}.region.role "${role}" is not a layer of this set (roles: ${roles.join(", ")})`
5869
+ );
5870
+ }
5871
+ const stop = resolveStop(name, target, at, ctx);
5872
+ const { points } = stop;
5873
+ const weights = regionWeights(points, box, x, y, falloff * ctx.faceWidth);
5874
+ const hit = weights.flatMap((w, k) => w > 0 ? [k] : []);
5875
+ if (hit.length === 0) {
5876
+ const fx = points.map(([px]) => (px - box.x0) / bw(box));
5877
+ const fy = points.map(([, py]) => (box.y1 - py) / bh(box));
5878
+ const range = (v) => `[${roundTo(Math.min(...v), 0.01)}, ${roundTo(Math.max(...v), 0.01)}]`;
5879
+ throw new PatchError(
5880
+ `${name}.region (falloff ${falloff}) catches none of ${target}'s ${points.length} points; at rest they span x ${range(fx)}, y ${range(fy)} of ${role}'s box`
5881
+ );
5882
+ }
5883
+ let sum = sums.get(stop.offsets);
5884
+ if (sum === void 0) {
5885
+ sum = {
5886
+ terms: stop.offsets.map(() => []),
5887
+ scale: stop.scale,
5888
+ step: stop.step
5889
+ };
5890
+ sums.set(stop.offsets, sum);
5891
+ }
5892
+ const ddx = dx * ctx.faceWidth;
5893
+ const ddy = -dy * ctx.faceWidth;
5894
+ for (const k of hit) {
5895
+ sum.terms[2 * k].push(ddx * weights[k]);
5896
+ sum.terms[2 * k + 1].push(ddy * weights[k]);
5897
+ }
5898
+ const maxW = Math.max(...hit.map((k) => weights[k]));
5899
+ const report = {
5900
+ index: i,
5901
+ target,
5902
+ at,
5903
+ moved: hit.length,
5904
+ of: points.length,
5905
+ maxPx: roundTo(maxW * Math.hypot(dx, dy) * ctx.faceWidth, 0.1)
5906
+ };
5907
+ if (stop.stride !== void 0) {
5908
+ const { stride } = stop;
5909
+ const cols = hit.map((k) => k % stride);
5910
+ const rows = hit.map((k) => Math.floor(k / stride));
5911
+ report.cols = [Math.min(...cols), Math.max(...cols)];
5912
+ report.rows = [Math.min(...rows), Math.max(...rows)];
5913
+ }
5914
+ return report;
5915
+ }
5916
+ function resolveStop(name, target, at, ctx) {
5917
+ const grid = ctx.grids.get(target);
5918
+ if (grid !== void 0) {
5919
+ const l = grid.lattice.points;
5920
+ const points = [];
5921
+ for (let k = 0; k < l.length; k += 2) points.push([l[k], l[k + 1]]);
5922
+ return {
5923
+ offsets: gridStop(name, at, target, grid.keyforms),
5924
+ points,
5925
+ scale: [1, 1],
5926
+ step: 0.1,
5927
+ stride: grid.lattice.cols + 1
5928
+ };
5929
+ }
5930
+ const part = ctx.parts.find((p) => p.id === target);
5931
+ const b = ctx.boxOf(target);
5932
+ if (part?.mesh === void 0 || part.warps === void 0 || b === void 0) {
5933
+ const parts = ctx.parts.filter(
5934
+ (p) => p.mesh !== void 0 && p.warps !== void 0 && ctx.boxOf(p.id) !== void 0
5935
+ );
5936
+ throw new PatchError(
5937
+ `${name}.target "${target}" is neither a feature grid (${[...ctx.grids.keys()].join(", ")}) nor a mesh part with warps (${parts.map((p) => p.id).join(", ")}); bodyWarp and the matrix deformers are not patchable`
5938
+ );
5939
+ }
5940
+ return {
5941
+ offsets: partStop(name, at, part, part.warps, ctx),
5942
+ points: meshPoints(part.mesh, b),
5943
+ scale: [bw(b), bh(b)],
5944
+ step: 1e-4
5945
+ };
5946
+ }
5947
+ function gridStop(name, at, id, keyforms) {
5948
+ const i = X_STOPS.indexOf(at[P9.AngleX]);
5949
+ const j = Y_STOPS.indexOf(at[P9.AngleY]);
5950
+ if (Object.keys(at).length !== 2 || i < 0 || j < 0) {
5951
+ throw new PatchError(
5952
+ `${name}.at must name ${P9.AngleX} in [${X_STOPS.join(", ")}] and ${P9.AngleY} in [${Y_STOPS.join(", ")}] for the grid ${id}, got ${JSON.stringify(at)}`
5953
+ );
5954
+ }
5955
+ if (X_STOPS[i] === 0 && Y_STOPS[j] === 0) {
5956
+ throw new PatchError(
5957
+ `${name}.at ${P9.AngleX}=0 ${P9.AngleY}=0 is ${id}'s rest stop: ${REST_IS_ART}; patch a stop with ${P9.AngleX} or ${P9.AngleY} at \xB130 instead`
5958
+ );
5959
+ }
5960
+ return keyforms[j * X_STOPS.length + i];
5961
+ }
5962
+ function partStop(name, at, part, warps, ctx) {
5963
+ const keys = Object.keys(at);
5964
+ const [param] = keys;
5965
+ const value = at[param];
5966
+ const keyform = keys.length === 1 ? warps.filter((w) => w.parameter === param).flatMap((w) => w.keyforms).find((k) => k.value === value) : void 0;
5967
+ if (keyform === void 0) {
5968
+ throw new PatchError(
5969
+ `${name}.at must name one parameter at one of ${part.id}'s warp stops (${listStops(warps, () => true)}), got ${JSON.stringify(at)}`
5970
+ );
5971
+ }
5972
+ if (ROLLS.has(param)) {
5973
+ throw new PatchError(
5974
+ `${name}.at ${param}: that parameter rolls ${part.id}'s deformer chain, so a displacement at its stop is not on screen axes; patch another of its stops (${listStops(warps, (p) => !ROLLS.has(p))}) or its feature grid instead`
5975
+ );
5976
+ }
5977
+ const isRest = (p, v) => v === ctx.parameters.find((q) => q.id === p)?.default;
5978
+ const isShut = (p, v) => SHUT_BY.get(part.id) === p && v === 0;
5979
+ if (!isRest(param, value) && !isShut(param, value)) return keyform.offsets;
5980
+ const open = listStops(warps, (p, v) => !isRest(p, v) && !isShut(p, v));
5981
+ const grid = part.deformer !== void 0 && ctx.grids.has(part.deformer) ? `its feature grid ${part.deformer} at a ${P9.AngleX} \xD7 ${P9.AngleY} stop` : "";
5982
+ const instead = [open && `another of its stops (${open})`, grid].filter((s) => s !== "").join(" or ");
5983
+ const hint = instead === "" ? "" : `; patch ${instead} instead`;
5984
+ throw new PatchError(
5985
+ isRest(param, value) ? `${name}.at ${param}=${value} is that parameter's default, ${part.id}'s rest stop: ${REST_IS_ART}${hint}` : `${name}.at ${param}=0 is where the rig shuts ${part.id} on purpose, a closure the fold check exempts, so a fold a patch made there would never be reported${hint}`
5986
+ );
5987
+ }
5988
+ function listStops(warps, keep) {
5989
+ const stops = /* @__PURE__ */ new Map();
5990
+ for (const w of warps) {
5991
+ for (const k of w.keyforms) {
5992
+ if (!keep(w.parameter, k.value)) continue;
5993
+ const s = stops.get(w.parameter) ?? /* @__PURE__ */ new Set();
5994
+ s.add(k.value);
5995
+ stops.set(w.parameter, s);
5996
+ }
5997
+ }
5998
+ return [...stops].map(([p, s]) => `${p}@${[...s].sort((a, b) => a - b).join(",")}`).join(" ");
5999
+ }
6000
+ function regionWeights(points, box, x, y, fall) {
6001
+ const x0 = box.x0 + x[0] * bw(box);
6002
+ const x1 = box.x0 + x[1] * bw(box);
6003
+ const top = box.y1 - y[0] * bh(box);
6004
+ const bottom = box.y1 - y[1] * bh(box);
6005
+ return points.map(([px, py]) => {
6006
+ const d = Math.hypot(
6007
+ Math.max(x0 - px, 0, px - x1),
6008
+ Math.max(bottom - py, 0, py - top)
6009
+ );
6010
+ return d === 0 ? 1 : d < fall ? smoothstep(fall, 0, d) : 0;
6011
+ });
6012
+ }
6013
+ function asPatch(check) {
6014
+ try {
6015
+ return check();
6016
+ } catch (e) {
6017
+ throw e instanceof TurnTargetError ? new PatchError(e.message) : e;
6018
+ }
6019
+ }
6020
+ function fields(name, v, known) {
6021
+ if (v === void 0) throw new PatchError(`${name} is missing`);
6022
+ asPatch(() => checkedKeys(name, v, known));
6023
+ return v;
6024
+ }
6025
+ function numbers(name, v) {
6026
+ const o = fields(name, v, Object.keys(v ?? {}));
6027
+ return Object.fromEntries(
6028
+ Object.entries(o).map(([k, n]) => [k, finite2(`${name}.${k}`, n)])
6029
+ );
6030
+ }
6031
+ var finite2 = (name, v, lo = -Infinity) => asPatch(() => checkedNumber(name, v, lo, Infinity));
6032
+ function text(name, v) {
6033
+ if (typeof v !== "string") {
6034
+ throw new PatchError(`${name} must be a string, got ${String(v)}`);
6035
+ }
6036
+ return v;
6037
+ }
6038
+ function span(name, v) {
6039
+ if (!Array.isArray(v) || v.length !== 2) {
6040
+ throw new PatchError(
6041
+ `${name} must be [from, to], got ${JSON.stringify(v)}`
6042
+ );
6043
+ }
6044
+ const lo = finite2(`${name}[0]`, v[0]);
6045
+ const hi = finite2(`${name}[1]`, v[1]);
6046
+ if (lo > hi) {
6047
+ throw new PatchError(
6048
+ `${name} [${lo}, ${hi}] runs backwards: ${name}[0] must not exceed ${name}[1]`
6049
+ );
6050
+ }
6051
+ return [lo, hi];
6052
+ }
6053
+
5774
6054
  // src/auto-rig/generate.ts
5775
6055
  var MESH_CELLS = {
5776
6056
  face: { px: 28, min: 4, max: 24 },
@@ -5828,7 +6108,7 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
5828
6108
  const solveFrame = buildHeadFrame(layers, frameOptions);
5829
6109
  const ctx = solveContext(solveFrame, byRole, turnOptions, hasNose);
5830
6110
  const boxes = Object.fromEntries(
5831
- ["eye_L", "eye_R", "brow_L", "brow_R"].filter(has).map((r) => [r, box(r)])
6111
+ ["eye_L", "eye_R", "iris_L", "iris_R", "brow_L", "brow_R"].filter(has).map((r) => [r, box(r)])
5832
6112
  );
5833
6113
  const landmarks = {
5834
6114
  boxes,
@@ -5948,6 +6228,15 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
5948
6228
  ];
5949
6229
  }
5950
6230
  const parameters = declareParameters(new Set(byRole.keys()));
6231
+ const reports = options.patches === void 0 ? void 0 : applyPatches(options.patches, {
6232
+ grids: new Map(
6233
+ [...grids].map(([family, g]) => [WARP_ID[family], g])
6234
+ ),
6235
+ parts,
6236
+ boxOf: (role) => has(role) ? box(role) : void 0,
6237
+ faceWidth: byRole.get("face").cropW,
6238
+ parameters
6239
+ });
5951
6240
  const model = {
5952
6241
  version: IKI_FORMAT_VERSION2,
5953
6242
  name: "auto-rigged",
@@ -5960,6 +6249,7 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
5960
6249
  motions: defaultMotions(new Set(parameters.map((p) => p.id)))
5961
6250
  };
5962
6251
  const valid = parseIkiModel3(model);
6252
+ if (reports !== void 0) options.onPatchesApplied?.(reports);
5963
6253
  if (hasNose && options.onTurnSolved) {
5964
6254
  options.onTurnSolved(
5965
6255
  turnReport(
@@ -5974,14 +6264,14 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
5974
6264
  function turnWarps(mesh, b, fieldOf) {
5975
6265
  return [
5976
6266
  localWarp(
5977
- P8.AngleX,
6267
+ P10.AngleX,
5978
6268
  mesh,
5979
6269
  b,
5980
6270
  X_STOPS,
5981
6271
  ([x, y], v, i) => v === 0 ? [0, 0] : [fieldOf(i)(x, y, v, 0)[0], 0]
5982
6272
  ),
5983
6273
  localWarp(
5984
- P8.AngleY,
6274
+ P10.AngleY,
5985
6275
  mesh,
5986
6276
  b,
5987
6277
  Y_STOPS,
@@ -6032,7 +6322,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
6032
6322
  }
6033
6323
  if (mesh !== void 0) part.mesh = mesh;
6034
6324
  const side = spec.role.endsWith("_L") ? "L" : "R";
6035
- const eyeOpen = side === "L" ? P8.EyeOpenLeft : P8.EyeOpenRight;
6325
+ const eyeOpen = side === "L" ? P10.EyeOpenLeft : P10.EyeOpenRight;
6036
6326
  const hh = frame.hh;
6037
6327
  switch (spec.role) {
6038
6328
  case "face":
@@ -6047,7 +6337,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
6047
6337
  headOwnRoll(byRole.has("body"))
6048
6338
  ),
6049
6339
  localWarp(
6050
- P8.Breath,
6340
+ P10.Breath,
6051
6341
  mesh,
6052
6342
  b,
6053
6343
  [0, 1],
@@ -6094,8 +6384,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
6094
6384
  if (byRole.has("mouth_open")) {
6095
6385
  warps.push(mouthWiden(mesh, b));
6096
6386
  bindings.push(
6097
- { parameter: P8.MouthOpen, channel: "opacity", from: 1, to: 0 },
6098
- { parameter: P8.MouthOpen, channel: "opacity", from: 1, to: 0 }
6387
+ { parameter: P10.MouthOpen, channel: "opacity", from: 1, to: 0 },
6388
+ { parameter: P10.MouthOpen, channel: "opacity", from: 1, to: 0 }
6099
6389
  );
6100
6390
  extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
6101
6391
  } else {
@@ -6105,19 +6395,19 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
6105
6395
  );
6106
6396
  bindings.push(
6107
6397
  {
6108
- parameter: P8.MouthOpen,
6398
+ parameter: P10.MouthOpen,
6109
6399
  channel: "scaleY",
6110
6400
  from: 0,
6111
6401
  to: roundTo(grow, 0.01)
6112
6402
  },
6113
6403
  {
6114
- parameter: P8.MouthOpen,
6404
+ parameter: P10.MouthOpen,
6115
6405
  channel: "scaleX",
6116
6406
  from: 0,
6117
6407
  to: roundTo(AMPLITUDE.mouthOpenWidth - 1, 0.01)
6118
6408
  },
6119
6409
  {
6120
- parameter: P8.MouthOpen,
6410
+ parameter: P10.MouthOpen,
6121
6411
  channel: "translateY",
6122
6412
  from: 0,
6123
6413
  to: -roundTo(grow * bh(b) / 2, 0.01)
@@ -6129,7 +6419,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
6129
6419
  case "mouth_open":
6130
6420
  warps.push(mouthForm(mesh, b), mouthOpenGrow(mesh, b));
6131
6421
  bindings.push({
6132
- parameter: P8.MouthOpen,
6422
+ parameter: P10.MouthOpen,
6133
6423
  channel: "opacity",
6134
6424
  from: 0,
6135
6425
  to: 1
@@ -6151,7 +6441,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
6151
6441
  case "blush_L":
6152
6442
  case "blush_R":
6153
6443
  bindings.push({
6154
- parameter: P8.Cheek,
6444
+ parameter: P10.Cheek,
6155
6445
  channel: "opacity",
6156
6446
  from: AMPLITUDE.blushRest,
6157
6447
  to: 1
@@ -6162,8 +6452,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
6162
6452
  if (byRole.has("hair_front")) {
6163
6453
  const amp = style.sway * AMPLITUDE.sway * hh;
6164
6454
  warps.push(
6165
- hairSway(P8.HairSwayX, mesh, b, amp),
6166
- hairSway(P8.HairSwayZ, mesh, b, amp)
6455
+ hairSway(P10.HairSwayX, mesh, b, amp),
6456
+ hairSway(P10.HairSwayZ, mesh, b, amp)
6167
6457
  );
6168
6458
  }
6169
6459
  warps.push(tiltHang(mesh, b, pivot, hairHangWeight(frame.chinY, b.y0)));
@@ -6178,6 +6468,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
6178
6468
  headStart
6179
6469
  };
6180
6470
  }
6471
+ var RAMP_ROWS = 3;
6472
+ var MIN_RAMP_CELL = 4;
6181
6473
  function hairFrontGrid(b, frame, boxes) {
6182
6474
  const cells = MESH_CELLS.hair_front;
6183
6475
  const px = Math.min(cells.px, meshScale("hair_front", frame));
@@ -6202,6 +6494,24 @@ function hairFrontGrid(b, frame, boxes) {
6202
6494
  { length: rows + 1 },
6203
6495
  (_, r) => b.y1 - bh(b) * r / rows
6204
6496
  );
6497
+ const eyes = [boxes.eye_L, boxes.eye_R].filter((e) => e !== void 0);
6498
+ const irises = [boxes.iris_L, boxes.iris_R].filter((e) => e !== void 0);
6499
+ if (eyes.length > 0 && irises.length > 0) {
6500
+ const eyeY = eyes.reduce((t, e) => t + (e.y0 + e.y1) / 2, 0) / eyes.length;
6501
+ const eyeH = eyes.reduce((t, e) => t + (e.y1 - e.y0), 0) / eyes.length;
6502
+ const hi = Math.min(eyeY + 1.5 * eyeH, b.y1);
6503
+ const lo = Math.max(Math.max(...irises.map((e) => e.y1)) - 0.5, b.y0);
6504
+ for (; ; ) {
6505
+ const edges = [hi, ...ys.filter((y) => y < hi && y > lo), lo];
6506
+ if (edges.length - 2 >= RAMP_ROWS) break;
6507
+ let gap = 0;
6508
+ for (let i = 1; i + 1 < edges.length; i++)
6509
+ if (edges[i] - edges[i + 1] > edges[gap] - edges[gap + 1]) gap = i;
6510
+ if (edges[gap] - edges[gap + 1] < 2 * MIN_RAMP_CELL) break;
6511
+ ys.push((edges[gap] + edges[gap + 1]) / 2);
6512
+ ys.sort((p, q) => q - p);
6513
+ }
6514
+ }
6205
6515
  return { xs, ys };
6206
6516
  }
6207
6517
  function meshScale(role, frame) {
@@ -6229,9 +6539,9 @@ function deformers(frame, grids, bodyWarp, arms, byRole) {
6229
6539
  pivot: { x: roundTo(frame.axisX, 0.01), y: roundTo(frame.chinY, 0.01) },
6230
6540
  bindings: [
6231
6541
  // AngleZ is clockwise-positive (Live2D's); a rotation is CCW-positive.
6232
- { parameter: P8.AngleZ, channel: "rotate", from: roll, to: -roll },
6542
+ { parameter: P10.AngleZ, channel: "rotate", from: roll, to: -roll },
6233
6543
  {
6234
- parameter: P8.Breath,
6544
+ parameter: P10.Breath,
6235
6545
  channel: "translateY",
6236
6546
  from: 0,
6237
6547
  to: roundTo(headOwnBreath(hasBody) * frame.hh, 0.01)
@@ -6249,8 +6559,8 @@ function deformers(frame, grids, bodyWarp, arms, byRole) {
6249
6559
  points: g.lattice.points
6250
6560
  },
6251
6561
  warp2d: {
6252
- parameter: P8.AngleX,
6253
- parameterY: P8.AngleY,
6562
+ parameter: P10.AngleX,
6563
+ parameterY: P10.AngleY,
6254
6564
  valuesX: X_STOPS,
6255
6565
  valuesY: Y_STOPS,
6256
6566
  keyforms2d: g.keyforms.map((offsets) => ({ offsets }))
@@ -6263,16 +6573,16 @@ function hairPhysics() {
6263
6573
  return [
6264
6574
  {
6265
6575
  id: "hairSway",
6266
- input: { parameter: P8.AngleX, weight: 1 },
6267
- output: { parameter: P8.HairSwayX, scale: 5 },
6576
+ input: { parameter: P10.AngleX, weight: 1 },
6577
+ output: { parameter: P10.HairSwayX, scale: 5 },
6268
6578
  mass: 1,
6269
6579
  stiffness: 30,
6270
6580
  damping: 3
6271
6581
  },
6272
6582
  {
6273
6583
  id: "hairTilt",
6274
- input: { parameter: P8.AngleZ, weight: 1 },
6275
- output: { parameter: P8.HairSwayZ, scale: 8 },
6584
+ input: { parameter: P10.AngleZ, weight: 1 },
6585
+ output: { parameter: P10.HairSwayZ, scale: 8 },
6276
6586
  mass: 1,
6277
6587
  stiffness: 25,
6278
6588
  damping: 2.5
@@ -6282,13 +6592,13 @@ function hairPhysics() {
6282
6592
  function declareParameters(roles) {
6283
6593
  const out = [];
6284
6594
  const add = (id, name, min, max, def) => out.push({ id, name, min, max, default: def });
6285
- add(P8.AngleX, "Head Angle", -30, 30, 0);
6286
- add(P8.AngleY, "Head Angle Y", -30, 30, 0);
6287
- add(P8.AngleZ, "Head Angle Z", -30, 30, 0);
6595
+ add(P10.AngleX, "Head Angle", -30, 30, 0);
6596
+ add(P10.AngleY, "Head Angle Y", -30, 30, 0);
6597
+ add(P10.AngleZ, "Head Angle Z", -30, 30, 0);
6288
6598
  if (roles.has("body")) {
6289
- add(P8.BodyAngleX, "Body Angle X", -10, 10, 0);
6290
- add(P8.BodyAngleY, "Body Angle Y", -10, 10, 0);
6291
- add(P8.BodyAngleZ, "Body Angle Z", -10, 10, 0);
6599
+ add(P10.BodyAngleX, "Body Angle X", -10, 10, 0);
6600
+ add(P10.BodyAngleY, "Body Angle Y", -10, 10, 0);
6601
+ add(P10.BodyAngleZ, "Body Angle Z", -10, 10, 0);
6292
6602
  }
6293
6603
  const addPose = (side, pose, angle) => {
6294
6604
  add(pose, `Arm Pose ${side}`, 0, 1, 0);
@@ -6297,40 +6607,40 @@ function declareParameters(roles) {
6297
6607
  const [armMin, armMax] = ARM_RANGE;
6298
6608
  const [elbowMin, elbowMax] = ELBOW_RANGE;
6299
6609
  if (roles.has("arm_L")) {
6300
- add(P8.ArmLeft, "Arm L", armMin, armMax, 0);
6301
- add(P8.ElbowLeft, "Elbow L", elbowMin, elbowMax, 0);
6610
+ add(P10.ArmLeft, "Arm L", armMin, armMax, 0);
6611
+ add(P10.ElbowLeft, "Elbow L", elbowMin, elbowMax, 0);
6302
6612
  if (roles.has("forearm_pose_L"))
6303
- addPose("L", P8.ArmPoseLeft, P8.ArmPoseAngleLeft);
6613
+ addPose("L", P10.ArmPoseLeft, P10.ArmPoseAngleLeft);
6304
6614
  }
6305
6615
  if (roles.has("arm_R")) {
6306
- add(P8.ArmRight, "Arm R", armMin, armMax, 0);
6307
- add(P8.ElbowRight, "Elbow R", elbowMin, elbowMax, 0);
6616
+ add(P10.ArmRight, "Arm R", armMin, armMax, 0);
6617
+ add(P10.ElbowRight, "Elbow R", elbowMin, elbowMax, 0);
6308
6618
  if (roles.has("forearm_pose_R"))
6309
- addPose("R", P8.ArmPoseRight, P8.ArmPoseAngleRight);
6619
+ addPose("R", P10.ArmPoseRight, P10.ArmPoseAngleRight);
6310
6620
  }
6311
- add(P8.EyeOpenLeft, "Eye L", 0, 1, 1);
6312
- add(P8.EyeOpenRight, "Eye R", 0, 1, 1);
6621
+ add(P10.EyeOpenLeft, "Eye L", 0, 1, 1);
6622
+ add(P10.EyeOpenRight, "Eye R", 0, 1, 1);
6313
6623
  if ([...roles].some((r) => /^(iris|pupil|highlight)_/.test(r))) {
6314
- add(P8.EyeballX, "Gaze X", -1, 1, 0);
6315
- add(P8.EyeballY, "Gaze Y", -1, 1, 0);
6624
+ add(P10.EyeballX, "Gaze X", -1, 1, 0);
6625
+ add(P10.EyeballY, "Gaze Y", -1, 1, 0);
6316
6626
  }
6317
- add(P8.MouthOpen, "Mouth Open", 0, 1, 0);
6318
- add(P8.MouthForm, "Mouth Form", -1, 1, 0);
6627
+ add(P10.MouthOpen, "Mouth Open", 0, 1, 0);
6628
+ add(P10.MouthForm, "Mouth Form", -1, 1, 0);
6319
6629
  if (roles.has("brow_L")) {
6320
- add(P8.BrowLeftY, "Brow L Y", -1, 1, 0);
6321
- add(P8.BrowLeftAngle, "Brow L Angle", -1, 1, 0);
6630
+ add(P10.BrowLeftY, "Brow L Y", -1, 1, 0);
6631
+ add(P10.BrowLeftAngle, "Brow L Angle", -1, 1, 0);
6322
6632
  }
6323
6633
  if (roles.has("brow_R")) {
6324
- add(P8.BrowRightY, "Brow R Y", -1, 1, 0);
6325
- add(P8.BrowRightAngle, "Brow R Angle", -1, 1, 0);
6634
+ add(P10.BrowRightY, "Brow R Y", -1, 1, 0);
6635
+ add(P10.BrowRightAngle, "Brow R Angle", -1, 1, 0);
6326
6636
  }
6327
6637
  if (roles.has("blush_L") || roles.has("blush_R")) {
6328
- add(P8.Cheek, "Cheek", 0, 1, 0);
6638
+ add(P10.Cheek, "Cheek", 0, 1, 0);
6329
6639
  }
6330
- add(P8.Breath, "Breath", 0, 1, 0);
6640
+ add(P10.Breath, "Breath", 0, 1, 0);
6331
6641
  if (roles.has("hair_front")) {
6332
- add(P8.HairSwayX, "Hair Sway X", -SWAY_RANGE, SWAY_RANGE, 0);
6333
- add(P8.HairSwayZ, "Hair Sway Z", -SWAY_RANGE, SWAY_RANGE, 0);
6642
+ add(P10.HairSwayX, "Hair Sway X", -SWAY_RANGE, SWAY_RANGE, 0);
6643
+ add(P10.HairSwayZ, "Hair Sway Z", -SWAY_RANGE, SWAY_RANGE, 0);
6334
6644
  }
6335
6645
  return out;
6336
6646
  }
@@ -6351,6 +6661,8 @@ export {
6351
6661
  LIP_INSIDE_ROLES,
6352
6662
  LIP_ROLES,
6353
6663
  LayerGeometryError,
6664
+ PatchError,
6665
+ SHUT_BY,
6354
6666
  SPECK_CORE_FRACTION,
6355
6667
  SetDeformerBindings,
6356
6668
  SetDeformerParent,