@ikijs/editor 0.19.0 → 0.21.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
@@ -1565,7 +1565,16 @@ var ROW_RUN_ROLES = /* @__PURE__ */ new Set([
1565
1565
  "hair_back",
1566
1566
  "body",
1567
1567
  "arm_L",
1568
- "arm_R"
1568
+ "arm_R",
1569
+ "forearm_pose_L",
1570
+ "forearm_pose_R",
1571
+ "mouth_inner",
1572
+ "lip_lower",
1573
+ "lip_upper"
1574
+ ]);
1575
+ var REST_HIDDEN_ROLES = /* @__PURE__ */ new Set([
1576
+ "forearm_pose_L",
1577
+ "forearm_pose_R"
1569
1578
  ]);
1570
1579
  function createLayerSetMeasurer(canvas) {
1571
1580
  for (const field of ["width", "height"]) {
@@ -1596,6 +1605,7 @@ function createLayerSetMeasurer(canvas) {
1596
1605
  if (bbox === null) return null;
1597
1606
  const rowLeft = new Int32Array(canvasH).fill(canvasW);
1598
1607
  const rowRight = new Int32Array(canvasH).fill(-1);
1608
+ const hidden = REST_HIDDEN_ROLES.has(role);
1599
1609
  const rowRuns = ROW_RUN_ROLES.has(role) ? [] : void 0;
1600
1610
  for (let y = 0; y < canvasH; y++) {
1601
1611
  let runs;
@@ -1611,14 +1621,14 @@ function createLayerSetMeasurer(canvas) {
1611
1621
  runs.push(x);
1612
1622
  inRun = on;
1613
1623
  }
1614
- if (!on) continue;
1624
+ if (!on || hidden) continue;
1615
1625
  opaque[p] = 1;
1616
1626
  if (x < rowLeft[y]) rowLeft[y] = x;
1617
1627
  if (x > rowRight[y]) rowRight[y] = x;
1618
1628
  }
1619
1629
  if (runs !== void 0 && inRun) runs.push(canvasW);
1620
1630
  }
1621
- rowSpansByRole.push({ role, rowLeft, rowRight });
1631
+ if (!hidden) rowSpansByRole.push({ role, rowLeft, rowRight });
1622
1632
  const layer = {
1623
1633
  role,
1624
1634
  fileName,
@@ -2177,6 +2187,29 @@ function gridMesh(cols, rows) {
2177
2187
  }
2178
2188
  return { vertices, uvs, indices };
2179
2189
  }
2190
+ function columnMesh(b, grid) {
2191
+ const { xs } = grid;
2192
+ const rows = grid.ys.length - 1;
2193
+ const vertices = [];
2194
+ const uvs = [];
2195
+ for (let r = 0; r <= rows; r++) {
2196
+ for (const x of xs) {
2197
+ const u = (x - b.x0) / bw(b);
2198
+ vertices.push(roundTo(u - 0.5, 1e-5), roundTo(0.5 - r / rows, 1e-5));
2199
+ uvs.push(roundTo(u, 1e-5), roundTo(r / rows, 1e-5));
2200
+ }
2201
+ }
2202
+ const n = xs.length;
2203
+ const indices = [];
2204
+ for (let r = 0; r < rows; r++) {
2205
+ for (let c = 0; c + 1 < n; c++) {
2206
+ const tl = r * n + c;
2207
+ const bl = tl + n;
2208
+ indices.push(bl, bl + 1, tl, tl, bl + 1, tl + 1);
2209
+ }
2210
+ }
2211
+ return { vertices, uvs, indices };
2212
+ }
2180
2213
  function meshPoints(mesh, box) {
2181
2214
  const out = [];
2182
2215
  for (let i = 0; i < mesh.vertices.length; i += 2) {
@@ -2302,10 +2335,13 @@ function browBindings(side, reach) {
2302
2335
  ];
2303
2336
  }
2304
2337
  var BROW_TILT = 15;
2305
- function mouthForm(mesh, box) {
2306
- const a = 0.3 * bh(box);
2307
- const c = cx(box);
2308
- const half = bw(box) / 2;
2338
+ var mouthFrameOf = (box) => ({
2339
+ c: cx(box),
2340
+ half: bw(box) / 2,
2341
+ amp: 0.3 * bh(box)
2342
+ });
2343
+ function mouthForm(mesh, box, frame = mouthFrameOf(box)) {
2344
+ const { c, half, amp: a } = frame;
2309
2345
  return localWarp(P.MouthForm, mesh, box, [-1, 0, 1], ([x], v) => {
2310
2346
  if (v === 0) return [0, 0];
2311
2347
  const u = (x - c) / half;
@@ -2323,8 +2359,8 @@ function mouthOpenGrow(mesh, box) {
2323
2359
  ([x, y], v) => v === 1 ? [(AMPLITUDE.mouthOpenWidth - 1) * (x - c), 0] : [0, box.y1 + (y - box.y1) * 0.2 - y]
2324
2360
  );
2325
2361
  }
2326
- function mouthWiden(mesh, box) {
2327
- const c = cx(box);
2362
+ function mouthWiden(mesh, box, frame = mouthFrameOf(box)) {
2363
+ const c = frame.c;
2328
2364
  return localWarp(P.MouthOpen, mesh, box, [0, 1], ([x], v) => [
2329
2365
  v * (AMPLITUDE.mouthOpenWidth - 1) * (x - c),
2330
2366
  0
@@ -2707,40 +2743,56 @@ var ARM_IDS = {
2707
2743
  forearmDeformer: "forearmDeformer_R"
2708
2744
  }
2709
2745
  };
2710
- function armGeometry(layer, bodyAxisX) {
2746
+ function endPivot(layer, end) {
2711
2747
  const { bbox, canvasW, canvasH, rowRuns } = layer;
2712
- const b = boxOfLayer(layer);
2713
- const span = (k) => {
2748
+ const span = runSpan(layer);
2749
+ const from = (k) => end === "top" ? k : bbox.h - 1 - k;
2750
+ const painted = rowRuns?.flatMap((runs, k) => runs.length > 0 ? [k] : []) ?? [];
2751
+ const firstK = end === "top" ? painted[0] ?? 0 : painted.at(-1) ?? bbox.h - 1;
2752
+ const lo = Math.ceil(CAP_ROWS[0] * bbox.h);
2753
+ const hi = Math.max(lo, Math.floor(CAP_ROWS[1] * bbox.h));
2754
+ const widths = [];
2755
+ for (let k = lo; k <= hi; k++) {
2756
+ const [s2, e2] = span(from(k));
2757
+ widths.push(e2 - s2);
2758
+ }
2759
+ widths.sort((p, q) => p - q);
2760
+ const radius = widths[widths.length >> 1] / 2;
2761
+ const ks = end === "top" ? firstK + radius : firstK - radius;
2762
+ const [s, e] = span(Math.round(ks));
2763
+ return {
2764
+ point: {
2765
+ x: roundTo((s + e) / 2 - canvasW / 2, 0.01),
2766
+ y: roundTo(canvasH / 2 - (bbox.y + ks + 0.5), 0.01)
2767
+ },
2768
+ radius,
2769
+ row: ks
2770
+ };
2771
+ }
2772
+ function runSpan(layer) {
2773
+ const { bbox, rowRuns } = layer;
2774
+ return (k) => {
2714
2775
  const runs = rowRuns?.[k] ?? [];
2715
2776
  if (runs.length === 0) return [bbox.x, bbox.x + bbox.w];
2716
- let [s2, e2] = [runs[0], runs[1]];
2777
+ let [s, e] = [runs[0], runs[1]];
2717
2778
  for (let i = 2; i < runs.length; i += 2) {
2718
- if (runs[i + 1] - runs[i] > e2 - s2) [s2, e2] = [runs[i], runs[i + 1]];
2779
+ if (runs[i + 1] - runs[i] > e - s) [s, e] = [runs[i], runs[i + 1]];
2719
2780
  }
2720
- return [s2, e2];
2781
+ return [s, e];
2721
2782
  };
2783
+ }
2784
+ function armGeometry(layer, bodyAxisX) {
2785
+ const { bbox, canvasW, canvasH, rowRuns } = layer;
2786
+ const b = boxOfLayer(layer);
2787
+ const span = runSpan(layer);
2722
2788
  const centreX = (k) => {
2723
2789
  const [s2, e2] = span(k);
2724
2790
  return (s2 + e2) / 2 - canvasW / 2;
2725
2791
  };
2726
2792
  const rowY = (k) => canvasH / 2 - (bbox.y + k + 0.5);
2727
2793
  const painted = rowRuns?.flatMap((runs, k) => runs.length > 0 ? [k] : []) ?? [];
2728
- const first = painted[0] ?? 0;
2729
2794
  const last = painted.at(-1) ?? bbox.h - 1;
2730
- const lo = Math.ceil(CAP_ROWS[0] * bbox.h);
2731
- const hi = Math.max(lo, Math.floor(CAP_ROWS[1] * bbox.h));
2732
- const widths = [];
2733
- for (let k = lo; k <= hi; k++) {
2734
- const [s2, e2] = span(k);
2735
- widths.push(e2 - s2);
2736
- }
2737
- widths.sort((p, q) => p - q);
2738
- const ru = widths[widths.length >> 1] / 2;
2739
- const ks = first + ru;
2740
- const shoulder = {
2741
- x: roundTo(centreX(Math.round(ks)), 0.01),
2742
- y: roundTo(rowY(ks), 0.01)
2743
- };
2795
+ const { point: shoulder, radius: ru, row: ks } = endPivot(layer, "top");
2744
2796
  const ke = Math.round(ks + ELBOW_AT * (last - ks));
2745
2797
  if (ke <= ks) {
2746
2798
  throw new LayerGeometryError(
@@ -2788,7 +2840,7 @@ function meshOf(vertices, indices) {
2788
2840
  );
2789
2841
  return { vertices, uvs, indices };
2790
2842
  }
2791
- function armParts(role, layer, g, order) {
2843
+ function armParts(role, layer, g, order, poseSwitch) {
2792
2844
  const b = boxOfLayer(layer);
2793
2845
  const lx = (x) => roundTo((x - cx(b)) / bw(b), 1e-5);
2794
2846
  const ly = (y) => roundTo((y - cy(b)) / bh(b), 1e-5);
@@ -2841,7 +2893,7 @@ function armParts(role, layer, g, order) {
2841
2893
  const [p, q] = [ringInner + j, ringOuter + j];
2842
2894
  ring.push(p, q, q + 1, p, q + 1, p + 1);
2843
2895
  }
2844
- const part = (id, deformer, z, mesh) => ({
2896
+ const part = (id, deformer, z, mesh, swaps = false) => ({
2845
2897
  id,
2846
2898
  color: [1, 1, 1, 1],
2847
2899
  width: bw(b),
@@ -2849,50 +2901,126 @@ function armParts(role, layer, g, order) {
2849
2901
  transform: { x: cx(b), y: cy(b) },
2850
2902
  order: z,
2851
2903
  deformer,
2852
- mesh
2904
+ mesh,
2905
+ ...swaps && poseSwitch !== void 0 ? {
2906
+ bindings: [
2907
+ {
2908
+ parameter: poseSwitch,
2909
+ channel: "opacity",
2910
+ from: 1,
2911
+ to: 0
2912
+ }
2913
+ ]
2914
+ } : {}
2853
2915
  });
2854
2916
  const ids = ARM_IDS[role];
2855
2917
  return [
2856
- part(ids.cap, ids.forearmDeformer, order, {
2857
- vertices,
2858
- uvs,
2859
- indices: [...fan, ...ring]
2860
- }),
2918
+ part(
2919
+ ids.cap,
2920
+ ids.forearmDeformer,
2921
+ order,
2922
+ {
2923
+ vertices,
2924
+ uvs,
2925
+ indices: [...fan, ...ring]
2926
+ },
2927
+ true
2928
+ ),
2861
2929
  part(role, ids.armDeformer, order + 1, meshOf(band(0.5, seam), quad)),
2862
2930
  part(
2863
2931
  ids.forearm,
2864
2932
  ids.forearmDeformer,
2865
2933
  order + 2,
2866
- meshOf(band(seam, -0.5), quad)
2934
+ meshOf(band(seam, -0.5), quad),
2935
+ true
2867
2936
  )
2868
2937
  ];
2869
2938
  }
2939
+ var rotateBinding = (parameter, range, sign) => ({
2940
+ parameter,
2941
+ channel: "rotate",
2942
+ from: sign * range[0],
2943
+ to: sign * range[1]
2944
+ });
2870
2945
  function armDeformers(role, g) {
2871
2946
  const ids = ARM_IDS[role];
2872
2947
  const params = ARM_PARAMS[role];
2873
- const rotate = (parameter, range, sign) => ({
2874
- parameter,
2875
- channel: "rotate",
2876
- from: sign * range[0],
2877
- to: sign * range[1]
2878
- });
2879
2948
  return [
2880
2949
  {
2881
2950
  id: ids.armDeformer,
2882
2951
  parent: BODY_WARP_ID,
2883
2952
  pivot: { ...g.shoulder },
2884
- bindings: [rotate(params.arm, ARM_RANGE, g.side)]
2953
+ bindings: [rotateBinding(params.arm, ARM_RANGE, g.side)]
2885
2954
  },
2886
2955
  {
2887
2956
  id: ids.forearmDeformer,
2888
2957
  parent: ids.armDeformer,
2889
2958
  pivot: { ...g.elbow },
2890
- bindings: [rotate(params.elbow, ELBOW_RANGE, -g.side)]
2959
+ bindings: [rotateBinding(params.elbow, ELBOW_RANGE, -g.side)]
2891
2960
  }
2892
2961
  ];
2893
2962
  }
2894
2963
 
2964
+ // src/auto-rig/forearm-pose.ts
2965
+ import {
2966
+ StandardParameter as P4
2967
+ } from "@ikijs/format";
2968
+ var ARM_OF_POSE = {
2969
+ forearm_pose_L: "arm_L",
2970
+ forearm_pose_R: "arm_R"
2971
+ };
2972
+ var POSE_OF_ARM = {
2973
+ arm_L: "forearm_pose_L",
2974
+ arm_R: "forearm_pose_R"
2975
+ };
2976
+ var POSE_PARAMS = {
2977
+ forearm_pose_L: { pose: P4.ArmPoseLeft, angle: P4.ArmPoseAngleLeft },
2978
+ forearm_pose_R: { pose: P4.ArmPoseRight, angle: P4.ArmPoseAngleRight }
2979
+ };
2980
+ var POSE_IDS = {
2981
+ forearm_pose_L: "armPoseDeformer_L",
2982
+ forearm_pose_R: "armPoseDeformer_R"
2983
+ };
2984
+ var POSE_ANGLE_RANGE = [-15, 15];
2985
+ function forearmPoseGeometry(layer) {
2986
+ const { point, radius } = endPivot(layer, "bottom");
2987
+ const first = layer.rowRuns?.findIndex((runs) => runs.length > 0) ?? -1;
2988
+ const k = first < 0 ? 0 : first;
2989
+ return {
2990
+ pivot: point,
2991
+ radius,
2992
+ top: roundTo(layer.canvasH / 2 - (layer.bbox.y + k + 0.5), 0.01)
2993
+ };
2994
+ }
2995
+ function forearmPosePart(role, layer, order) {
2996
+ const b = boxOfLayer(layer);
2997
+ return {
2998
+ id: role,
2999
+ color: [1, 1, 1, 1],
3000
+ width: bw(b),
3001
+ height: bh(b),
3002
+ transform: { x: cx(b), y: cy(b) },
3003
+ order,
3004
+ deformer: POSE_IDS[role],
3005
+ bindings: [
3006
+ { parameter: POSE_PARAMS[role].pose, channel: "opacity", from: 0, to: 1 }
3007
+ ]
3008
+ };
3009
+ }
3010
+ function forearmPoseDeformer(role, arm) {
3011
+ return {
3012
+ id: POSE_IDS[role],
3013
+ parent: ARM_IDS[ARM_OF_POSE[role]].armDeformer,
3014
+ pivot: { ...arm.elbow },
3015
+ bindings: [
3016
+ rotateBinding(POSE_PARAMS[role].angle, POSE_ANGLE_RANGE, -arm.side)
3017
+ ]
3018
+ };
3019
+ }
3020
+
2895
3021
  // src/auto-rig/roles.ts
3022
+ var LIP_ROLES = ["mouth_inner", "lip_lower", "lip_upper"];
3023
+ var isLipRole = (role) => LIP_ROLES.includes(role);
2896
3024
  var ROLE_TABLE = [
2897
3025
  { role: "hair_back", family: "hair_back" },
2898
3026
  { role: "body", family: "body" },
@@ -2902,8 +3030,14 @@ var ROLE_TABLE = [
2902
3030
  { role: "blush_L", family: "face" },
2903
3031
  { role: "blush_R", family: "face" },
2904
3032
  { role: "nose", family: "nose" },
2905
- { role: "mouth", family: "mouth", required: true },
3033
+ { role: "mouth", family: "mouth" },
2906
3034
  { role: "mouth_open", family: "mouth" },
3035
+ // The lip set, back to front: the interior, the lower lip's skin, the upper
3036
+ // lip and its line over both. It folds open instead of crossfading
3037
+ // (`mouth.ts`), so it stands in for `mouth`, never beside it.
3038
+ { role: "mouth_inner", family: "mouth" },
3039
+ { role: "lip_lower", family: "mouth" },
3040
+ { role: "lip_upper", family: "mouth" },
2907
3041
  { role: "eye_L", family: "eye_L", required: true },
2908
3042
  { role: "eye_R", family: "eye_R", required: true },
2909
3043
  { role: "iris_L", family: "eye_L" },
@@ -2918,7 +3052,10 @@ var ROLE_TABLE = [
2918
3052
  { role: "lash_R", family: "eye_R" },
2919
3053
  { role: "brow_L", family: "brow_L" },
2920
3054
  { role: "brow_R", family: "brow_R" },
2921
- { role: "hair_front", family: "hair_front" }
3055
+ { role: "hair_front", family: "hair_front" },
3056
+ // Drawn above the face and the hair: a raised hand passes them.
3057
+ { role: "forearm_pose_L", family: "forearm_pose" },
3058
+ { role: "forearm_pose_R", family: "forearm_pose" }
2922
3059
  ];
2923
3060
  var REQUIRED_ROLES = ROLE_TABLE.filter(
2924
3061
  (r) => r.required
@@ -2958,6 +3095,39 @@ function checkArmsHaveBody(roles) {
2958
3095
  );
2959
3096
  }
2960
3097
  }
3098
+ function checkMouth(roles) {
3099
+ const has = new Set(roles);
3100
+ const present = LIP_ROLES.filter((r) => has.has(r));
3101
+ if (present.length === 0) {
3102
+ if (!has.has("mouth")) {
3103
+ throw new Error(
3104
+ `auto-rig: missing required role mouth (or the lip set ${LIP_ROLES.join(", ")})`
3105
+ );
3106
+ }
3107
+ return;
3108
+ }
3109
+ if (present.length < LIP_ROLES.length) {
3110
+ throw new Error(
3111
+ `auto-rig: the lip set is partial: missing ${LIP_ROLES.filter((r) => !has.has(r)).join(", ")}`
3112
+ );
3113
+ }
3114
+ const mixed = ["mouth", "mouth_open"].filter((r) => has.has(r));
3115
+ if (mixed.length > 0) {
3116
+ throw new Error(
3117
+ `auto-rig: the lip set cannot be mixed with ${mixed.join(", ")} (the lip set replaces the mouth drawings; use one or the other)`
3118
+ );
3119
+ }
3120
+ }
3121
+ function checkPosesHaveArms(roles) {
3122
+ const has = new Set(roles);
3123
+ for (const side of ["L", "R"]) {
3124
+ if (has.has(`forearm_pose_${side}`) && !has.has(`arm_${side}`)) {
3125
+ throw new Error(
3126
+ `auto-rig: forearm_pose_${side} needs an arm_${side} layer (a pose forearm hangs from its arm's elbow)`
3127
+ );
3128
+ }
3129
+ }
3130
+ }
2961
3131
  function parseLayerRoles(fileNames) {
2962
3132
  const seen = /* @__PURE__ */ new Map();
2963
3133
  const out = [];
@@ -2983,43 +3153,45 @@ function parseLayerRoles(fileNames) {
2983
3153
  `auto-rig: missing required role${missing.length > 1 ? "s" : ""} ${missing.join(", ")}`
2984
3154
  );
2985
3155
  }
3156
+ checkMouth(seen.keys());
2986
3157
  checkArmsHaveBody(seen.keys());
3158
+ checkPosesHaveArms(seen.keys());
2987
3159
  return out;
2988
3160
  }
2989
3161
 
2990
3162
  // src/auto-rig/generate.ts
2991
3163
  import {
2992
3164
  IKI_FORMAT_VERSION as IKI_FORMAT_VERSION2,
2993
- StandardParameter as P6,
3165
+ StandardParameter as P8,
2994
3166
  parseIkiModel as parseIkiModel3
2995
3167
  } from "@ikijs/format";
2996
3168
 
2997
3169
  // src/auto-rig/animations.ts
2998
3170
  import {
2999
- StandardParameter as P4
3171
+ StandardParameter as P5
3000
3172
  } from "@ikijs/format";
3001
3173
  function browsY(y) {
3002
3174
  return [
3003
- { parameter: P4.BrowLeftY, value: y, blend: "add" },
3004
- { parameter: P4.BrowRightY, value: y, blend: "add" }
3175
+ { parameter: P5.BrowLeftY, value: y, blend: "add" },
3176
+ { parameter: P5.BrowRightY, value: y, blend: "add" }
3005
3177
  ];
3006
3178
  }
3007
3179
  function browsTilt(innerDown) {
3008
3180
  return [
3009
- { parameter: P4.BrowLeftAngle, value: innerDown, blend: "add" },
3010
- { parameter: P4.BrowRightAngle, value: -innerDown, blend: "add" }
3181
+ { parameter: P5.BrowLeftAngle, value: innerDown, blend: "add" },
3182
+ { parameter: P5.BrowRightAngle, value: -innerDown, blend: "add" }
3011
3183
  ];
3012
3184
  }
3013
3185
  function cheek(value) {
3014
- return { parameter: P4.Cheek, value, blend: "add" };
3186
+ return { parameter: P5.Cheek, value, blend: "add" };
3015
3187
  }
3016
- var BROWS = [P4.BrowLeftY, P4.BrowRightY, P4.BrowLeftAngle, P4.BrowRightAngle];
3188
+ var BROWS = [P5.BrowLeftY, P5.BrowRightY, P5.BrowLeftAngle, P5.BrowRightAngle];
3017
3189
  var EXPRESSIONS = [
3018
3190
  {
3019
3191
  id: "smile",
3020
3192
  description: "Smiling and pleased: warm, friendly, content. For greetings, thanks, agreement and gentle happiness.",
3021
3193
  parameters: [
3022
- { parameter: P4.MouthForm, value: 1, blend: "add" },
3194
+ { parameter: P5.MouthForm, value: 1, blend: "add" },
3023
3195
  ...browsY(0.25),
3024
3196
  cheek(0.4)
3025
3197
  ]
@@ -3028,10 +3200,10 @@ var EXPRESSIONS = [
3028
3200
  id: "laugh",
3029
3201
  description: "Laughing, eyes shut and mouth open: delighted, amused. For jokes, playfulness and big joy.",
3030
3202
  parameters: [
3031
- { parameter: P4.EyeOpenLeft, value: 0, blend: "multiply" },
3032
- { parameter: P4.EyeOpenRight, value: 0, blend: "multiply" },
3033
- { parameter: P4.MouthForm, value: 1, blend: "add" },
3034
- { parameter: P4.MouthOpen, value: 0.8, blend: "overwrite" },
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" },
3035
3207
  ...browsY(0.4),
3036
3208
  cheek(0.65)
3037
3209
  ]
@@ -3043,7 +3215,7 @@ var EXPRESSIONS = [
3043
3215
  parameters: [
3044
3216
  ...browsY(-0.5),
3045
3217
  ...browsTilt(0.8),
3046
- { parameter: P4.MouthForm, value: -0.9, blend: "add" }
3218
+ { parameter: P5.MouthForm, value: -0.9, blend: "add" }
3047
3219
  ]
3048
3220
  },
3049
3221
  {
@@ -3053,7 +3225,7 @@ var EXPRESSIONS = [
3053
3225
  parameters: [
3054
3226
  ...browsY(0.35),
3055
3227
  ...browsTilt(-0.8),
3056
- { parameter: P4.MouthForm, value: -0.8, blend: "add" }
3228
+ { parameter: P5.MouthForm, value: -0.8, blend: "add" }
3057
3229
  ]
3058
3230
  },
3059
3231
  {
@@ -3061,18 +3233,18 @@ var EXPRESSIONS = [
3061
3233
  description: "Surprised, mouth dropped open: startled, amazed. For shock, sudden news or disbelief.",
3062
3234
  parameters: [
3063
3235
  ...browsY(0.8),
3064
- { parameter: P4.MouthOpen, value: 0.9, blend: "overwrite" }
3236
+ { parameter: P5.MouthOpen, value: 0.9, blend: "overwrite" }
3065
3237
  ]
3066
3238
  },
3067
3239
  {
3068
3240
  id: "shy",
3069
3241
  description: "Shy or embarrassed: bashful, flustered. For being praised, teased or caught off guard.",
3070
- needs: [P4.Cheek],
3242
+ needs: [P5.Cheek],
3071
3243
  parameters: [
3072
3244
  cheek(1),
3073
3245
  // Gaze down.
3074
- { parameter: P4.EyeballY, value: -0.65, blend: "add" },
3075
- { parameter: P4.MouthForm, value: 0.4, blend: "add" },
3246
+ { parameter: P5.EyeballY, value: -0.65, blend: "add" },
3247
+ { parameter: P5.MouthForm, value: 0.4, blend: "add" },
3076
3248
  ...browsTilt(-0.25)
3077
3249
  ]
3078
3250
  }
@@ -3085,14 +3257,14 @@ function defaultExpressions(declared) {
3085
3257
  parameters: e.parameters.filter((t) => declared.has(t.parameter))
3086
3258
  }));
3087
3259
  }
3088
- var DEFAULT_MOTIONS = {
3260
+ var HEAD_MOTIONS = {
3089
3261
  Nod: [
3090
3262
  {
3091
3263
  description: "Nods yes: agreement, acknowledgement, understanding.",
3092
3264
  duration: 1,
3093
3265
  curves: [
3094
3266
  {
3095
- parameter: P4.AngleY,
3267
+ parameter: P5.AngleY,
3096
3268
  keys: [
3097
3269
  [0, 0],
3098
3270
  [0.3, -18],
@@ -3113,7 +3285,7 @@ var DEFAULT_MOTIONS = {
3113
3285
  fadeIn: 0.15,
3114
3286
  curves: [
3115
3287
  {
3116
- parameter: P4.AngleX,
3288
+ parameter: P5.AngleX,
3117
3289
  keys: [
3118
3290
  [0, 0],
3119
3291
  [0.2, -18],
@@ -3132,7 +3304,7 @@ var DEFAULT_MOTIONS = {
3132
3304
  duration: 1.4,
3133
3305
  curves: [
3134
3306
  {
3135
- parameter: P4.AngleZ,
3307
+ parameter: P5.AngleZ,
3136
3308
  keys: [
3137
3309
  [0, 0],
3138
3310
  [0.35, 20],
@@ -3144,6 +3316,55 @@ var DEFAULT_MOTIONS = {
3144
3316
  }
3145
3317
  ]
3146
3318
  };
3319
+ var WAVE = {
3320
+ Wave: [
3321
+ {
3322
+ description: "Waves hello with the right hand: greeting, goodbye, getting attention.",
3323
+ duration: 2.2,
3324
+ fadeIn: 0.15,
3325
+ fadeOut: 0.15,
3326
+ curves: [
3327
+ {
3328
+ parameter: P5.ArmPoseRight,
3329
+ interpolation: "linear",
3330
+ keys: [
3331
+ [0, 0],
3332
+ [0.15, 1],
3333
+ [2.05, 1],
3334
+ [2.2, 0]
3335
+ ]
3336
+ },
3337
+ {
3338
+ parameter: P5.ArmRight,
3339
+ keys: [
3340
+ [0, 0],
3341
+ [0.4, 10],
3342
+ [1.8, 10],
3343
+ [2.2, 0]
3344
+ ]
3345
+ },
3346
+ {
3347
+ parameter: P5.ArmPoseAngleRight,
3348
+ keys: [
3349
+ [0, 0],
3350
+ [0.5, 0],
3351
+ [0.7, 12],
3352
+ [1, -12],
3353
+ [1.3, 12],
3354
+ [1.7, 0],
3355
+ [2.2, 0]
3356
+ ]
3357
+ }
3358
+ ]
3359
+ }
3360
+ ]
3361
+ };
3362
+ function defaultMotions(declared) {
3363
+ const waves = [P5.ArmRight, P5.ArmPoseRight, P5.ArmPoseAngleRight].every(
3364
+ (id) => declared.has(id)
3365
+ );
3366
+ return waves ? { ...HEAD_MOTIONS, ...WAVE } : HEAD_MOTIONS;
3367
+ }
3147
3368
 
3148
3369
  // src/auto-rig/checks.ts
3149
3370
  var finite = (v) => typeof v === "number" && Number.isFinite(v);
@@ -3248,7 +3469,16 @@ function checkLayers(layers, canvas) {
3248
3469
  if (missing.length > 0) {
3249
3470
  throw new Error(`auto-rig: missing required role(s) ${missing.join(", ")}`);
3250
3471
  }
3472
+ checkMouth(seen);
3473
+ for (const l of layers) {
3474
+ if (l.rowRuns === void 0 && isLipRole(l.role)) {
3475
+ throw new Error(
3476
+ `auto-rig: layer "${l.fileName}": the lip set needs rowRuns (the fold reads the opening's rows off them; measure with createLayerSetMeasurer)`
3477
+ );
3478
+ }
3479
+ }
3251
3480
  checkArmsHaveBody(seen);
3481
+ checkPosesHaveArms(seen);
3252
3482
  }
3253
3483
  function checkHeadEdges(edges, byRole) {
3254
3484
  if (edges === void 0) return;
@@ -3366,7 +3596,182 @@ function checkStrandEdges(strand, irises, hair) {
3366
3596
  }
3367
3597
 
3368
3598
  // src/auto-rig/context.ts
3369
- import { StandardParameter as P5 } from "@ikijs/format";
3599
+ import { StandardParameter as P7 } from "@ikijs/format";
3600
+
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
+ }
3370
3775
 
3371
3776
  // src/auto-rig/head.ts
3372
3777
  function outerEdge(runs, axisX, side) {
@@ -3446,7 +3851,7 @@ function buildHeadFrame(layers, opts) {
3446
3851
  return ya === void 0 || yb === void 0 ? void 0 : (ya + yb) / 2;
3447
3852
  };
3448
3853
  const eyeY = pair("iris_L", "iris_R") ?? pair("eye_L", "eye_R");
3449
- const mouthY = centreY("mouth");
3854
+ const mouthY = mouthAnchor(byRole).at.y;
3450
3855
  const rowY = (r) => canvasH / 2 - (faceLayer.bbox.y + r + 0.5);
3451
3856
  const raw = filledProfile(faceLayer.rowHalfWidths);
3452
3857
  const profile = raw === null ? null : smoothProfile(raw);
@@ -4398,6 +4803,7 @@ function familyField(m, family) {
4398
4803
  return hairField(m, m.hairBack, NOD.hairBack);
4399
4804
  case "body":
4400
4805
  case "arm":
4806
+ case "forearm_pose":
4401
4807
  return () => [0, 0];
4402
4808
  }
4403
4809
  }
@@ -4418,7 +4824,6 @@ function sideIrises(byRole) {
4418
4824
  return pair === void 0 ? {} : { left: pair.left.box, right: pair.right.box };
4419
4825
  }
4420
4826
  function solveContext(frame, byRole, options, hasNose) {
4421
- const box = (role) => boxOfLayer(byRole.get(role));
4422
4827
  const row = (s, strand2, sideSign) => {
4423
4828
  if (strand2 !== void 0) {
4424
4829
  const lo = sideSign < 0 ? strand2.irisOuter : strand2.irisInner;
@@ -4467,8 +4872,7 @@ function solveContext(frame, byRole, options, hasNose) {
4467
4872
  noseBox = nose.denseCore ? boxOfPixels(nose.denseCore, nose.canvasW, nose.canvasH) : boxOfLayer(nose);
4468
4873
  noseAt = { x: cx(noseBox), y: cy(noseBox) };
4469
4874
  }
4470
- const mouthBox = byRole.has("mouth_open") ? unionBoxes([box("mouth"), box("mouth_open")]) : box("mouth");
4471
- const m = box("mouth");
4875
+ const mouth = mouthAnchor(byRole);
4472
4876
  return {
4473
4877
  frame,
4474
4878
  left,
@@ -4480,8 +4884,8 @@ function solveContext(frame, byRole, options, hasNose) {
4480
4884
  eyeBoxes: { left: eyes.left.box, right: eyes.right.box },
4481
4885
  noseAt,
4482
4886
  noseBox,
4483
- mouthAt: { x: cx(m), y: cy(m) },
4484
- mouthBox
4887
+ mouthAt: mouth.at,
4888
+ mouthBox: mouth.box
4485
4889
  };
4486
4890
  }
4487
4891
  function renderedLander(turn, parts, grids, from = /* @__PURE__ */ new Map()) {
@@ -4497,7 +4901,7 @@ function renderedLander(turn, parts, grids, from = /* @__PURE__ */ new Map()) {
4497
4901
  const family = roleSpec(role).family;
4498
4902
  const part = parts.find((p) => p.id === role);
4499
4903
  const g = grids.get(family);
4500
- const warp = part?.warps?.find((w) => w.parameter === P5.AngleX);
4904
+ const warp = part?.warps?.find((w) => w.parameter === P7.AngleX);
4501
4905
  if (part === void 0 || part.mesh === void 0 || !g && !warp) {
4502
4906
  return x + field(role)(x, y, ax, 0)[0];
4503
4907
  }
@@ -5233,6 +5637,7 @@ var WARP_ID = {
5233
5637
  };
5234
5638
  var isGridFamily = (f) => f in WARP_ID;
5235
5639
  var isArm = (spec) => spec.family === "arm";
5640
+ var isPose = (spec) => spec.family === "forearm_pose";
5236
5641
  var GRID_PAD = 6;
5237
5642
  function generateIkiFromLayerSet(layers, canvas, options = {}) {
5238
5643
  checkLayers(layers, canvas);
@@ -5293,6 +5698,7 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
5293
5698
  hh: frame.hh,
5294
5699
  pivots: [...arms.values()].map((g) => g.shoulder)
5295
5700
  }) : void 0;
5701
+ const lips = isLipSet(byRole) ? buildMouthRig(byRole) : void 0;
5296
5702
  const parts = [];
5297
5703
  const reach = /* @__PURE__ */ new Map();
5298
5704
  let regionOf = [];
@@ -5301,11 +5707,22 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
5301
5707
  const layer = byRole.get(spec.role);
5302
5708
  if (layer === void 0) continue;
5303
5709
  if (isArm(spec)) {
5710
+ const pose = POSE_OF_ARM[spec.role];
5304
5711
  parts.push(
5305
- ...armParts(spec.role, layer, arms.get(spec.role), parts.length)
5712
+ ...armParts(
5713
+ spec.role,
5714
+ layer,
5715
+ arms.get(spec.role),
5716
+ parts.length,
5717
+ has(pose) ? POSE_PARAMS[pose].pose : void 0
5718
+ )
5306
5719
  );
5307
5720
  continue;
5308
5721
  }
5722
+ if (isPose(spec)) {
5723
+ parts.push(forearmPosePart(spec.role, layer, parts.length));
5724
+ continue;
5725
+ }
5309
5726
  const built = buildPart(
5310
5727
  spec,
5311
5728
  boxOfLayer(layer),
@@ -5313,7 +5730,8 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
5313
5730
  frame,
5314
5731
  byRole,
5315
5732
  style,
5316
- landmarks.hairFrontGrid
5733
+ landmarks.hairFrontGrid,
5734
+ lips
5317
5735
  );
5318
5736
  if (built.region !== void 0) {
5319
5737
  regionOf = built.region;
@@ -5358,7 +5776,7 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
5358
5776
  part.mesh = bodyWarp.mesh;
5359
5777
  continue;
5360
5778
  }
5361
- if (family === "arm") continue;
5779
+ if (family === "arm" || family === "forearm_pose") continue;
5362
5780
  part.deformer = "headDeformer";
5363
5781
  if (part.mesh === void 0) continue;
5364
5782
  const own = familyField(turn, family);
@@ -5375,10 +5793,10 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
5375
5793
  canvas: { width: canvas.width, height: canvas.height },
5376
5794
  parameters,
5377
5795
  parts,
5378
- deformers: deformers(frame, grids, bodyWarp?.deformer, arms),
5796
+ deformers: deformers(frame, grids, bodyWarp?.deformer, arms, byRole),
5379
5797
  ...has("hair_front") ? { physics: hairPhysics() } : {},
5380
5798
  expressions: defaultExpressions(new Set(parameters.map((p) => p.id))),
5381
- motions: DEFAULT_MOTIONS
5799
+ motions: defaultMotions(new Set(parameters.map((p) => p.id)))
5382
5800
  };
5383
5801
  const valid = parseIkiModel3(model);
5384
5802
  if (hasNose && options.onTurnSolved) {
@@ -5395,14 +5813,14 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
5395
5813
  function turnWarps(mesh, b, fieldOf) {
5396
5814
  return [
5397
5815
  localWarp(
5398
- P6.AngleX,
5816
+ P8.AngleX,
5399
5817
  mesh,
5400
5818
  b,
5401
5819
  X_STOPS,
5402
5820
  ([x, y], v, i) => v === 0 ? [0, 0] : [fieldOf(i)(x, y, v, 0)[0], 0]
5403
5821
  ),
5404
5822
  localWarp(
5405
- P6.AngleY,
5823
+ P8.AngleY,
5406
5824
  mesh,
5407
5825
  b,
5408
5826
  Y_STOPS,
@@ -5415,7 +5833,7 @@ function plateFields(turn, region, head) {
5415
5833
  const neck = neckField(turn);
5416
5834
  return (i) => region[i] === "ear" ? ear : region[i] === "neck" ? neck : head;
5417
5835
  }
5418
- function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5836
+ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
5419
5837
  const box = (role) => boxOfLayer(byRole.get(role));
5420
5838
  const part = {
5421
5839
  id: spec.role,
@@ -5430,6 +5848,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5430
5848
  const bindings = [];
5431
5849
  let extra = [0, 0];
5432
5850
  let mesh;
5851
+ let knotXs;
5433
5852
  let region;
5434
5853
  let headStart = 0;
5435
5854
  if (spec.role === "face") {
@@ -5440,6 +5859,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5440
5859
  headStart = fm.headStart;
5441
5860
  } else if (spec.role === "hair_front") {
5442
5861
  mesh = columnMesh(b, frontGrid);
5862
+ } else if (lips !== void 0 && isLipRole(spec.role)) {
5863
+ ({ mesh, xs: knotXs } = mouthMesh(b, lips.knots));
5443
5864
  } else if (spec.family !== "body") {
5444
5865
  const cells = MESH_CELLS[spec.role] ?? FEATURE_MESH_CELLS;
5445
5866
  const px = Math.min(cells.px, meshScale(spec.role, frame));
@@ -5450,7 +5871,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5450
5871
  }
5451
5872
  if (mesh !== void 0) part.mesh = mesh;
5452
5873
  const side = spec.role.endsWith("_L") ? "L" : "R";
5453
- const eyeOpen = side === "L" ? P6.EyeOpenLeft : P6.EyeOpenRight;
5874
+ const eyeOpen = side === "L" ? P8.EyeOpenLeft : P8.EyeOpenRight;
5454
5875
  const hh = frame.hh;
5455
5876
  switch (spec.role) {
5456
5877
  case "face":
@@ -5465,7 +5886,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5465
5886
  headOwnRoll(byRole.has("body"))
5466
5887
  ),
5467
5888
  localWarp(
5468
- P6.Breath,
5889
+ P8.Breath,
5469
5890
  mesh,
5470
5891
  b,
5471
5892
  [0, 1],
@@ -5512,8 +5933,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5512
5933
  if (byRole.has("mouth_open")) {
5513
5934
  warps.push(mouthWiden(mesh, b));
5514
5935
  bindings.push(
5515
- { parameter: P6.MouthOpen, channel: "opacity", from: 1, to: 0 },
5516
- { parameter: P6.MouthOpen, channel: "opacity", from: 1, to: 0 }
5936
+ { parameter: P8.MouthOpen, channel: "opacity", from: 1, to: 0 },
5937
+ { parameter: P8.MouthOpen, channel: "opacity", from: 1, to: 0 }
5517
5938
  );
5518
5939
  extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
5519
5940
  } else {
@@ -5523,19 +5944,19 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5523
5944
  );
5524
5945
  bindings.push(
5525
5946
  {
5526
- parameter: P6.MouthOpen,
5947
+ parameter: P8.MouthOpen,
5527
5948
  channel: "scaleY",
5528
5949
  from: 0,
5529
5950
  to: roundTo(grow, 0.01)
5530
5951
  },
5531
5952
  {
5532
- parameter: P6.MouthOpen,
5953
+ parameter: P8.MouthOpen,
5533
5954
  channel: "scaleX",
5534
5955
  from: 0,
5535
5956
  to: roundTo(AMPLITUDE.mouthOpenWidth - 1, 0.01)
5536
5957
  },
5537
5958
  {
5538
- parameter: P6.MouthOpen,
5959
+ parameter: P8.MouthOpen,
5539
5960
  channel: "translateY",
5540
5961
  from: 0,
5541
5962
  to: -roundTo(grow * bh(b) / 2, 0.01)
@@ -5547,17 +5968,26 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5547
5968
  case "mouth_open":
5548
5969
  warps.push(mouthForm(mesh, b), mouthOpenGrow(mesh, b));
5549
5970
  bindings.push({
5550
- parameter: P6.MouthOpen,
5971
+ parameter: P8.MouthOpen,
5551
5972
  channel: "opacity",
5552
5973
  from: 0,
5553
5974
  to: 1
5554
5975
  });
5555
5976
  extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
5556
5977
  break;
5978
+ case "mouth_inner":
5979
+ case "lip_lower":
5980
+ case "lip_upper":
5981
+ warps.push(
5982
+ mouthFoldWarp(spec.role, mesh, knotXs, b, lips.opening),
5983
+ mouthForm(mesh, b, lips.frame),
5984
+ mouthWiden(mesh, b, lips.frame)
5985
+ );
5986
+ break;
5557
5987
  case "blush_L":
5558
5988
  case "blush_R":
5559
5989
  bindings.push({
5560
- parameter: P6.Cheek,
5990
+ parameter: P8.Cheek,
5561
5991
  channel: "opacity",
5562
5992
  from: AMPLITUDE.blushRest,
5563
5993
  to: 1
@@ -5568,8 +5998,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5568
5998
  if (byRole.has("hair_front")) {
5569
5999
  const amp = style.sway * AMPLITUDE.sway * hh;
5570
6000
  warps.push(
5571
- hairSway(P6.HairSwayX, mesh, b, amp),
5572
- hairSway(P6.HairSwayZ, mesh, b, amp)
6001
+ hairSway(P8.HairSwayX, mesh, b, amp),
6002
+ hairSway(P8.HairSwayZ, mesh, b, amp)
5573
6003
  );
5574
6004
  }
5575
6005
  warps.push(tiltHang(mesh, b, pivot, hairHangWeight(frame.chinY, b.y0)));
@@ -5610,29 +6040,6 @@ function hairFrontGrid(b, frame, boxes) {
5610
6040
  );
5611
6041
  return { xs, ys };
5612
6042
  }
5613
- function columnMesh(b, grid) {
5614
- const { xs } = grid;
5615
- const rows = grid.ys.length - 1;
5616
- const vertices = [];
5617
- const uvs = [];
5618
- for (let r = 0; r <= rows; r++) {
5619
- for (const x of xs) {
5620
- const u = (x - b.x0) / bw(b);
5621
- vertices.push(roundTo(u - 0.5, 1e-5), roundTo(0.5 - r / rows, 1e-5));
5622
- uvs.push(roundTo(u, 1e-5), roundTo(r / rows, 1e-5));
5623
- }
5624
- }
5625
- const n = xs.length;
5626
- const indices = [];
5627
- for (let r = 0; r < rows; r++) {
5628
- for (let c = 0; c + 1 < n; c++) {
5629
- const tl = r * n + c;
5630
- const bl = tl + n;
5631
- indices.push(bl, bl + 1, tl, tl, bl + 1, tl + 1);
5632
- }
5633
- }
5634
- return { vertices, uvs, indices };
5635
- }
5636
6043
  function meshScale(role, frame) {
5637
6044
  switch (role) {
5638
6045
  case "hair_front":
@@ -5642,10 +6049,14 @@ function meshScale(role, frame) {
5642
6049
  return Infinity;
5643
6050
  }
5644
6051
  }
5645
- function deformers(frame, grids, bodyWarp, arms) {
6052
+ function deformers(frame, grids, bodyWarp, arms, byRole) {
5646
6053
  const out = [];
5647
6054
  if (bodyWarp !== void 0) out.push(bodyWarp);
5648
6055
  for (const [role, g] of arms) out.push(...armDeformers(role, g));
6056
+ for (const [role, arm] of arms) {
6057
+ const pose = POSE_OF_ARM[role];
6058
+ if (byRole.has(pose)) out.push(forearmPoseDeformer(pose, arm));
6059
+ }
5649
6060
  const hasBody = bodyWarp !== void 0;
5650
6061
  const roll = headOwnRoll(hasBody);
5651
6062
  out.push({
@@ -5654,9 +6065,9 @@ function deformers(frame, grids, bodyWarp, arms) {
5654
6065
  pivot: { x: roundTo(frame.axisX, 0.01), y: roundTo(frame.chinY, 0.01) },
5655
6066
  bindings: [
5656
6067
  // AngleZ is clockwise-positive (Live2D's); a rotation is CCW-positive.
5657
- { parameter: P6.AngleZ, channel: "rotate", from: roll, to: -roll },
6068
+ { parameter: P8.AngleZ, channel: "rotate", from: roll, to: -roll },
5658
6069
  {
5659
- parameter: P6.Breath,
6070
+ parameter: P8.Breath,
5660
6071
  channel: "translateY",
5661
6072
  from: 0,
5662
6073
  to: roundTo(headOwnBreath(hasBody) * frame.hh, 0.01)
@@ -5674,8 +6085,8 @@ function deformers(frame, grids, bodyWarp, arms) {
5674
6085
  points: g.lattice.points
5675
6086
  },
5676
6087
  warp2d: {
5677
- parameter: P6.AngleX,
5678
- parameterY: P6.AngleY,
6088
+ parameter: P8.AngleX,
6089
+ parameterY: P8.AngleY,
5679
6090
  valuesX: X_STOPS,
5680
6091
  valuesY: Y_STOPS,
5681
6092
  keyforms2d: g.keyforms.map((offsets) => ({ offsets }))
@@ -5688,16 +6099,16 @@ function hairPhysics() {
5688
6099
  return [
5689
6100
  {
5690
6101
  id: "hairSway",
5691
- input: { parameter: P6.AngleX, weight: 1 },
5692
- output: { parameter: P6.HairSwayX, scale: 5 },
6102
+ input: { parameter: P8.AngleX, weight: 1 },
6103
+ output: { parameter: P8.HairSwayX, scale: 5 },
5693
6104
  mass: 1,
5694
6105
  stiffness: 30,
5695
6106
  damping: 3
5696
6107
  },
5697
6108
  {
5698
6109
  id: "hairTilt",
5699
- input: { parameter: P6.AngleZ, weight: 1 },
5700
- output: { parameter: P6.HairSwayZ, scale: 8 },
6110
+ input: { parameter: P8.AngleZ, weight: 1 },
6111
+ output: { parameter: P8.HairSwayZ, scale: 8 },
5701
6112
  mass: 1,
5702
6113
  stiffness: 25,
5703
6114
  damping: 2.5
@@ -5707,47 +6118,55 @@ function hairPhysics() {
5707
6118
  function declareParameters(roles) {
5708
6119
  const out = [];
5709
6120
  const add = (id, name, min, max, def) => out.push({ id, name, min, max, default: def });
5710
- add(P6.AngleX, "Head Angle", -30, 30, 0);
5711
- add(P6.AngleY, "Head Angle Y", -30, 30, 0);
5712
- add(P6.AngleZ, "Head Angle Z", -30, 30, 0);
6121
+ add(P8.AngleX, "Head Angle", -30, 30, 0);
6122
+ add(P8.AngleY, "Head Angle Y", -30, 30, 0);
6123
+ add(P8.AngleZ, "Head Angle Z", -30, 30, 0);
5713
6124
  if (roles.has("body")) {
5714
- add(P6.BodyAngleX, "Body Angle X", -10, 10, 0);
5715
- add(P6.BodyAngleY, "Body Angle Y", -10, 10, 0);
5716
- add(P6.BodyAngleZ, "Body Angle Z", -10, 10, 0);
6125
+ add(P8.BodyAngleX, "Body Angle X", -10, 10, 0);
6126
+ add(P8.BodyAngleY, "Body Angle Y", -10, 10, 0);
6127
+ add(P8.BodyAngleZ, "Body Angle Z", -10, 10, 0);
5717
6128
  }
6129
+ const addPose = (side, pose, angle) => {
6130
+ add(pose, `Arm Pose ${side}`, 0, 1, 0);
6131
+ add(angle, `Arm Pose Angle ${side}`, ...POSE_ANGLE_RANGE, 0);
6132
+ };
5718
6133
  const [armMin, armMax] = ARM_RANGE;
5719
6134
  const [elbowMin, elbowMax] = ELBOW_RANGE;
5720
6135
  if (roles.has("arm_L")) {
5721
- add(P6.ArmLeft, "Arm L", armMin, armMax, 0);
5722
- add(P6.ElbowLeft, "Elbow L", elbowMin, elbowMax, 0);
6136
+ add(P8.ArmLeft, "Arm L", armMin, armMax, 0);
6137
+ add(P8.ElbowLeft, "Elbow L", elbowMin, elbowMax, 0);
6138
+ if (roles.has("forearm_pose_L"))
6139
+ addPose("L", P8.ArmPoseLeft, P8.ArmPoseAngleLeft);
5723
6140
  }
5724
6141
  if (roles.has("arm_R")) {
5725
- add(P6.ArmRight, "Arm R", armMin, armMax, 0);
5726
- add(P6.ElbowRight, "Elbow R", elbowMin, elbowMax, 0);
6142
+ add(P8.ArmRight, "Arm R", armMin, armMax, 0);
6143
+ add(P8.ElbowRight, "Elbow R", elbowMin, elbowMax, 0);
6144
+ if (roles.has("forearm_pose_R"))
6145
+ addPose("R", P8.ArmPoseRight, P8.ArmPoseAngleRight);
5727
6146
  }
5728
- add(P6.EyeOpenLeft, "Eye L", 0, 1, 1);
5729
- add(P6.EyeOpenRight, "Eye R", 0, 1, 1);
6147
+ add(P8.EyeOpenLeft, "Eye L", 0, 1, 1);
6148
+ add(P8.EyeOpenRight, "Eye R", 0, 1, 1);
5730
6149
  if ([...roles].some((r) => /^(iris|pupil|highlight)_/.test(r))) {
5731
- add(P6.EyeballX, "Gaze X", -1, 1, 0);
5732
- add(P6.EyeballY, "Gaze Y", -1, 1, 0);
6150
+ add(P8.EyeballX, "Gaze X", -1, 1, 0);
6151
+ add(P8.EyeballY, "Gaze Y", -1, 1, 0);
5733
6152
  }
5734
- add(P6.MouthOpen, "Mouth Open", 0, 1, 0);
5735
- add(P6.MouthForm, "Mouth Form", -1, 1, 0);
6153
+ add(P8.MouthOpen, "Mouth Open", 0, 1, 0);
6154
+ add(P8.MouthForm, "Mouth Form", -1, 1, 0);
5736
6155
  if (roles.has("brow_L")) {
5737
- add(P6.BrowLeftY, "Brow L Y", -1, 1, 0);
5738
- add(P6.BrowLeftAngle, "Brow L Angle", -1, 1, 0);
6156
+ add(P8.BrowLeftY, "Brow L Y", -1, 1, 0);
6157
+ add(P8.BrowLeftAngle, "Brow L Angle", -1, 1, 0);
5739
6158
  }
5740
6159
  if (roles.has("brow_R")) {
5741
- add(P6.BrowRightY, "Brow R Y", -1, 1, 0);
5742
- add(P6.BrowRightAngle, "Brow R Angle", -1, 1, 0);
6160
+ add(P8.BrowRightY, "Brow R Y", -1, 1, 0);
6161
+ add(P8.BrowRightAngle, "Brow R Angle", -1, 1, 0);
5743
6162
  }
5744
6163
  if (roles.has("blush_L") || roles.has("blush_R")) {
5745
- add(P6.Cheek, "Cheek", 0, 1, 0);
6164
+ add(P8.Cheek, "Cheek", 0, 1, 0);
5746
6165
  }
5747
- add(P6.Breath, "Breath", 0, 1, 0);
6166
+ add(P8.Breath, "Breath", 0, 1, 0);
5748
6167
  if (roles.has("hair_front")) {
5749
- add(P6.HairSwayX, "Hair Sway X", -SWAY_RANGE, SWAY_RANGE, 0);
5750
- add(P6.HairSwayZ, "Hair Sway Z", -SWAY_RANGE, SWAY_RANGE, 0);
6168
+ add(P8.HairSwayX, "Hair Sway X", -SWAY_RANGE, SWAY_RANGE, 0);
6169
+ add(P8.HairSwayZ, "Hair Sway Z", -SWAY_RANGE, SWAY_RANGE, 0);
5751
6170
  }
5752
6171
  return out;
5753
6172
  }
@@ -5794,6 +6213,7 @@ export {
5794
6213
  createLayerSetMeasurer,
5795
6214
  denseCoreOf,
5796
6215
  detectAlphaBbox,
6216
+ forearmPoseGeometry,
5797
6217
  foregroundSpan,
5798
6218
  generateIkiFromLayerSet,
5799
6219
  headHalfOf,