@ikijs/editor 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -62,6 +62,7 @@ __export(index_exports, {
62
62
  createLayerSetMeasurer: () => createLayerSetMeasurer,
63
63
  denseCoreOf: () => denseCoreOf,
64
64
  detectAlphaBbox: () => detectAlphaBbox,
65
+ forearmPoseGeometry: () => forearmPoseGeometry,
65
66
  foregroundSpan: () => foregroundSpan,
66
67
  generateIkiFromLayerSet: () => generateIkiFromLayerSet,
67
68
  headHalfOf: () => headHalfOf,
@@ -1641,7 +1642,13 @@ var ROW_RUN_ROLES = /* @__PURE__ */ new Set([
1641
1642
  "hair_back",
1642
1643
  "body",
1643
1644
  "arm_L",
1644
- "arm_R"
1645
+ "arm_R",
1646
+ "forearm_pose_L",
1647
+ "forearm_pose_R"
1648
+ ]);
1649
+ var REST_HIDDEN_ROLES = /* @__PURE__ */ new Set([
1650
+ "forearm_pose_L",
1651
+ "forearm_pose_R"
1645
1652
  ]);
1646
1653
  function createLayerSetMeasurer(canvas) {
1647
1654
  for (const field of ["width", "height"]) {
@@ -1672,6 +1679,7 @@ function createLayerSetMeasurer(canvas) {
1672
1679
  if (bbox === null) return null;
1673
1680
  const rowLeft = new Int32Array(canvasH).fill(canvasW);
1674
1681
  const rowRight = new Int32Array(canvasH).fill(-1);
1682
+ const hidden = REST_HIDDEN_ROLES.has(role);
1675
1683
  const rowRuns = ROW_RUN_ROLES.has(role) ? [] : void 0;
1676
1684
  for (let y = 0; y < canvasH; y++) {
1677
1685
  let runs;
@@ -1687,14 +1695,14 @@ function createLayerSetMeasurer(canvas) {
1687
1695
  runs.push(x);
1688
1696
  inRun = on;
1689
1697
  }
1690
- if (!on) continue;
1698
+ if (!on || hidden) continue;
1691
1699
  opaque[p] = 1;
1692
1700
  if (x < rowLeft[y]) rowLeft[y] = x;
1693
1701
  if (x > rowRight[y]) rowRight[y] = x;
1694
1702
  }
1695
1703
  if (runs !== void 0 && inRun) runs.push(canvasW);
1696
1704
  }
1697
- rowSpansByRole.push({ role, rowLeft, rowRight });
1705
+ if (!hidden) rowSpansByRole.push({ role, rowLeft, rowRight });
1698
1706
  const layer = {
1699
1707
  role,
1700
1708
  fileName,
@@ -2777,40 +2785,56 @@ var ARM_IDS = {
2777
2785
  forearmDeformer: "forearmDeformer_R"
2778
2786
  }
2779
2787
  };
2780
- function armGeometry(layer, bodyAxisX) {
2788
+ function endPivot(layer, end) {
2781
2789
  const { bbox, canvasW, canvasH, rowRuns } = layer;
2782
- const b = boxOfLayer(layer);
2783
- const span = (k) => {
2790
+ const span = runSpan(layer);
2791
+ const from = (k) => end === "top" ? k : bbox.h - 1 - k;
2792
+ const painted = rowRuns?.flatMap((runs, k) => runs.length > 0 ? [k] : []) ?? [];
2793
+ const firstK = end === "top" ? painted[0] ?? 0 : painted.at(-1) ?? bbox.h - 1;
2794
+ const lo = Math.ceil(CAP_ROWS[0] * bbox.h);
2795
+ const hi = Math.max(lo, Math.floor(CAP_ROWS[1] * bbox.h));
2796
+ const widths = [];
2797
+ for (let k = lo; k <= hi; k++) {
2798
+ const [s2, e2] = span(from(k));
2799
+ widths.push(e2 - s2);
2800
+ }
2801
+ widths.sort((p, q) => p - q);
2802
+ const radius = widths[widths.length >> 1] / 2;
2803
+ const ks = end === "top" ? firstK + radius : firstK - radius;
2804
+ const [s, e] = span(Math.round(ks));
2805
+ return {
2806
+ point: {
2807
+ x: roundTo((s + e) / 2 - canvasW / 2, 0.01),
2808
+ y: roundTo(canvasH / 2 - (bbox.y + ks + 0.5), 0.01)
2809
+ },
2810
+ radius,
2811
+ row: ks
2812
+ };
2813
+ }
2814
+ function runSpan(layer) {
2815
+ const { bbox, rowRuns } = layer;
2816
+ return (k) => {
2784
2817
  const runs = rowRuns?.[k] ?? [];
2785
2818
  if (runs.length === 0) return [bbox.x, bbox.x + bbox.w];
2786
- let [s2, e2] = [runs[0], runs[1]];
2819
+ let [s, e] = [runs[0], runs[1]];
2787
2820
  for (let i = 2; i < runs.length; i += 2) {
2788
- if (runs[i + 1] - runs[i] > e2 - s2) [s2, e2] = [runs[i], runs[i + 1]];
2821
+ if (runs[i + 1] - runs[i] > e - s) [s, e] = [runs[i], runs[i + 1]];
2789
2822
  }
2790
- return [s2, e2];
2823
+ return [s, e];
2791
2824
  };
2825
+ }
2826
+ function armGeometry(layer, bodyAxisX) {
2827
+ const { bbox, canvasW, canvasH, rowRuns } = layer;
2828
+ const b = boxOfLayer(layer);
2829
+ const span = runSpan(layer);
2792
2830
  const centreX = (k) => {
2793
2831
  const [s2, e2] = span(k);
2794
2832
  return (s2 + e2) / 2 - canvasW / 2;
2795
2833
  };
2796
2834
  const rowY = (k) => canvasH / 2 - (bbox.y + k + 0.5);
2797
2835
  const painted = rowRuns?.flatMap((runs, k) => runs.length > 0 ? [k] : []) ?? [];
2798
- const first = painted[0] ?? 0;
2799
2836
  const last = painted.at(-1) ?? bbox.h - 1;
2800
- const lo = Math.ceil(CAP_ROWS[0] * bbox.h);
2801
- const hi = Math.max(lo, Math.floor(CAP_ROWS[1] * bbox.h));
2802
- const widths = [];
2803
- for (let k = lo; k <= hi; k++) {
2804
- const [s2, e2] = span(k);
2805
- widths.push(e2 - s2);
2806
- }
2807
- widths.sort((p, q) => p - q);
2808
- const ru = widths[widths.length >> 1] / 2;
2809
- const ks = first + ru;
2810
- const shoulder = {
2811
- x: roundTo(centreX(Math.round(ks)), 0.01),
2812
- y: roundTo(rowY(ks), 0.01)
2813
- };
2837
+ const { point: shoulder, radius: ru, row: ks } = endPivot(layer, "top");
2814
2838
  const ke = Math.round(ks + ELBOW_AT * (last - ks));
2815
2839
  if (ke <= ks) {
2816
2840
  throw new LayerGeometryError(
@@ -2858,7 +2882,7 @@ function meshOf(vertices, indices) {
2858
2882
  );
2859
2883
  return { vertices, uvs, indices };
2860
2884
  }
2861
- function armParts(role, layer, g, order) {
2885
+ function armParts(role, layer, g, order, poseSwitch) {
2862
2886
  const b = boxOfLayer(layer);
2863
2887
  const lx = (x) => roundTo((x - cx(b)) / bw(b), 1e-5);
2864
2888
  const ly = (y) => roundTo((y - cy(b)) / bh(b), 1e-5);
@@ -2911,7 +2935,7 @@ function armParts(role, layer, g, order) {
2911
2935
  const [p, q] = [ringInner + j, ringOuter + j];
2912
2936
  ring.push(p, q, q + 1, p, q + 1, p + 1);
2913
2937
  }
2914
- const part = (id, deformer, z, mesh) => ({
2938
+ const part = (id, deformer, z, mesh, swaps = false) => ({
2915
2939
  id,
2916
2940
  color: [1, 1, 1, 1],
2917
2941
  width: bw(b),
@@ -2919,49 +2943,121 @@ function armParts(role, layer, g, order) {
2919
2943
  transform: { x: cx(b), y: cy(b) },
2920
2944
  order: z,
2921
2945
  deformer,
2922
- mesh
2946
+ mesh,
2947
+ ...swaps && poseSwitch !== void 0 ? {
2948
+ bindings: [
2949
+ {
2950
+ parameter: poseSwitch,
2951
+ channel: "opacity",
2952
+ from: 1,
2953
+ to: 0
2954
+ }
2955
+ ]
2956
+ } : {}
2923
2957
  });
2924
2958
  const ids = ARM_IDS[role];
2925
2959
  return [
2926
- part(ids.cap, ids.forearmDeformer, order, {
2927
- vertices,
2928
- uvs,
2929
- indices: [...fan, ...ring]
2930
- }),
2960
+ part(
2961
+ ids.cap,
2962
+ ids.forearmDeformer,
2963
+ order,
2964
+ {
2965
+ vertices,
2966
+ uvs,
2967
+ indices: [...fan, ...ring]
2968
+ },
2969
+ true
2970
+ ),
2931
2971
  part(role, ids.armDeformer, order + 1, meshOf(band(0.5, seam), quad)),
2932
2972
  part(
2933
2973
  ids.forearm,
2934
2974
  ids.forearmDeformer,
2935
2975
  order + 2,
2936
- meshOf(band(seam, -0.5), quad)
2976
+ meshOf(band(seam, -0.5), quad),
2977
+ true
2937
2978
  )
2938
2979
  ];
2939
2980
  }
2981
+ var rotateBinding = (parameter, range, sign) => ({
2982
+ parameter,
2983
+ channel: "rotate",
2984
+ from: sign * range[0],
2985
+ to: sign * range[1]
2986
+ });
2940
2987
  function armDeformers(role, g) {
2941
2988
  const ids = ARM_IDS[role];
2942
2989
  const params = ARM_PARAMS[role];
2943
- const rotate = (parameter, range, sign) => ({
2944
- parameter,
2945
- channel: "rotate",
2946
- from: sign * range[0],
2947
- to: sign * range[1]
2948
- });
2949
2990
  return [
2950
2991
  {
2951
2992
  id: ids.armDeformer,
2952
2993
  parent: BODY_WARP_ID,
2953
2994
  pivot: { ...g.shoulder },
2954
- bindings: [rotate(params.arm, ARM_RANGE, g.side)]
2995
+ bindings: [rotateBinding(params.arm, ARM_RANGE, g.side)]
2955
2996
  },
2956
2997
  {
2957
2998
  id: ids.forearmDeformer,
2958
2999
  parent: ids.armDeformer,
2959
3000
  pivot: { ...g.elbow },
2960
- bindings: [rotate(params.elbow, ELBOW_RANGE, -g.side)]
3001
+ bindings: [rotateBinding(params.elbow, ELBOW_RANGE, -g.side)]
2961
3002
  }
2962
3003
  ];
2963
3004
  }
2964
3005
 
3006
+ // src/auto-rig/forearm-pose.ts
3007
+ var import_format6 = require("@ikijs/format");
3008
+ var ARM_OF_POSE = {
3009
+ forearm_pose_L: "arm_L",
3010
+ forearm_pose_R: "arm_R"
3011
+ };
3012
+ var POSE_OF_ARM = {
3013
+ arm_L: "forearm_pose_L",
3014
+ arm_R: "forearm_pose_R"
3015
+ };
3016
+ var POSE_PARAMS = {
3017
+ forearm_pose_L: { pose: import_format6.StandardParameter.ArmPoseLeft, angle: import_format6.StandardParameter.ArmPoseAngleLeft },
3018
+ forearm_pose_R: { pose: import_format6.StandardParameter.ArmPoseRight, angle: import_format6.StandardParameter.ArmPoseAngleRight }
3019
+ };
3020
+ var POSE_IDS = {
3021
+ forearm_pose_L: "armPoseDeformer_L",
3022
+ forearm_pose_R: "armPoseDeformer_R"
3023
+ };
3024
+ var POSE_ANGLE_RANGE = [-15, 15];
3025
+ function forearmPoseGeometry(layer) {
3026
+ const { point, radius } = endPivot(layer, "bottom");
3027
+ const first = layer.rowRuns?.findIndex((runs) => runs.length > 0) ?? -1;
3028
+ const k = first < 0 ? 0 : first;
3029
+ return {
3030
+ pivot: point,
3031
+ radius,
3032
+ top: roundTo(layer.canvasH / 2 - (layer.bbox.y + k + 0.5), 0.01)
3033
+ };
3034
+ }
3035
+ function forearmPosePart(role, layer, order) {
3036
+ const b = boxOfLayer(layer);
3037
+ return {
3038
+ id: role,
3039
+ color: [1, 1, 1, 1],
3040
+ width: bw(b),
3041
+ height: bh(b),
3042
+ transform: { x: cx(b), y: cy(b) },
3043
+ order,
3044
+ deformer: POSE_IDS[role],
3045
+ bindings: [
3046
+ { parameter: POSE_PARAMS[role].pose, channel: "opacity", from: 0, to: 1 }
3047
+ ]
3048
+ };
3049
+ }
3050
+ function forearmPoseDeformer(role, arm) {
3051
+ return {
3052
+ id: POSE_IDS[role],
3053
+ parent: ARM_IDS[ARM_OF_POSE[role]].armDeformer,
3054
+ pivot: { ...arm.elbow },
3055
+ bindings: [
3056
+ rotateBinding(POSE_PARAMS[role].angle, POSE_ANGLE_RANGE, -arm.side)
3057
+ ]
3058
+ };
3059
+ }
3060
+
2965
3061
  // src/auto-rig/roles.ts
2966
3062
  var ROLE_TABLE = [
2967
3063
  { role: "hair_back", family: "hair_back" },
@@ -2988,7 +3084,10 @@ var ROLE_TABLE = [
2988
3084
  { role: "lash_R", family: "eye_R" },
2989
3085
  { role: "brow_L", family: "brow_L" },
2990
3086
  { role: "brow_R", family: "brow_R" },
2991
- { role: "hair_front", family: "hair_front" }
3087
+ { role: "hair_front", family: "hair_front" },
3088
+ // Drawn above the face and the hair: a raised hand passes them.
3089
+ { role: "forearm_pose_L", family: "forearm_pose" },
3090
+ { role: "forearm_pose_R", family: "forearm_pose" }
2992
3091
  ];
2993
3092
  var REQUIRED_ROLES = ROLE_TABLE.filter(
2994
3093
  (r) => r.required
@@ -3028,6 +3127,16 @@ function checkArmsHaveBody(roles) {
3028
3127
  );
3029
3128
  }
3030
3129
  }
3130
+ function checkPosesHaveArms(roles) {
3131
+ const has = new Set(roles);
3132
+ for (const side of ["L", "R"]) {
3133
+ if (has.has(`forearm_pose_${side}`) && !has.has(`arm_${side}`)) {
3134
+ throw new Error(
3135
+ `auto-rig: forearm_pose_${side} needs an arm_${side} layer (a pose forearm hangs from its arm's elbow)`
3136
+ );
3137
+ }
3138
+ }
3139
+ }
3031
3140
  function parseLayerRoles(fileNames) {
3032
3141
  const seen = /* @__PURE__ */ new Map();
3033
3142
  const out = [];
@@ -3054,36 +3163,37 @@ function parseLayerRoles(fileNames) {
3054
3163
  );
3055
3164
  }
3056
3165
  checkArmsHaveBody(seen.keys());
3166
+ checkPosesHaveArms(seen.keys());
3057
3167
  return out;
3058
3168
  }
3059
3169
 
3060
3170
  // src/auto-rig/generate.ts
3061
- var import_format8 = require("@ikijs/format");
3171
+ var import_format9 = require("@ikijs/format");
3062
3172
 
3063
3173
  // src/auto-rig/animations.ts
3064
- var import_format6 = require("@ikijs/format");
3174
+ var import_format7 = require("@ikijs/format");
3065
3175
  function browsY(y) {
3066
3176
  return [
3067
- { parameter: import_format6.StandardParameter.BrowLeftY, value: y, blend: "add" },
3068
- { parameter: import_format6.StandardParameter.BrowRightY, value: y, blend: "add" }
3177
+ { parameter: import_format7.StandardParameter.BrowLeftY, value: y, blend: "add" },
3178
+ { parameter: import_format7.StandardParameter.BrowRightY, value: y, blend: "add" }
3069
3179
  ];
3070
3180
  }
3071
3181
  function browsTilt(innerDown) {
3072
3182
  return [
3073
- { parameter: import_format6.StandardParameter.BrowLeftAngle, value: innerDown, blend: "add" },
3074
- { parameter: import_format6.StandardParameter.BrowRightAngle, value: -innerDown, blend: "add" }
3183
+ { parameter: import_format7.StandardParameter.BrowLeftAngle, value: innerDown, blend: "add" },
3184
+ { parameter: import_format7.StandardParameter.BrowRightAngle, value: -innerDown, blend: "add" }
3075
3185
  ];
3076
3186
  }
3077
3187
  function cheek(value) {
3078
- return { parameter: import_format6.StandardParameter.Cheek, value, blend: "add" };
3188
+ return { parameter: import_format7.StandardParameter.Cheek, value, blend: "add" };
3079
3189
  }
3080
- var BROWS = [import_format6.StandardParameter.BrowLeftY, import_format6.StandardParameter.BrowRightY, import_format6.StandardParameter.BrowLeftAngle, import_format6.StandardParameter.BrowRightAngle];
3190
+ var BROWS = [import_format7.StandardParameter.BrowLeftY, import_format7.StandardParameter.BrowRightY, import_format7.StandardParameter.BrowLeftAngle, import_format7.StandardParameter.BrowRightAngle];
3081
3191
  var EXPRESSIONS = [
3082
3192
  {
3083
3193
  id: "smile",
3084
3194
  description: "Smiling and pleased: warm, friendly, content. For greetings, thanks, agreement and gentle happiness.",
3085
3195
  parameters: [
3086
- { parameter: import_format6.StandardParameter.MouthForm, value: 1, blend: "add" },
3196
+ { parameter: import_format7.StandardParameter.MouthForm, value: 1, blend: "add" },
3087
3197
  ...browsY(0.25),
3088
3198
  cheek(0.4)
3089
3199
  ]
@@ -3092,10 +3202,10 @@ var EXPRESSIONS = [
3092
3202
  id: "laugh",
3093
3203
  description: "Laughing, eyes shut and mouth open: delighted, amused. For jokes, playfulness and big joy.",
3094
3204
  parameters: [
3095
- { parameter: import_format6.StandardParameter.EyeOpenLeft, value: 0, blend: "multiply" },
3096
- { parameter: import_format6.StandardParameter.EyeOpenRight, value: 0, blend: "multiply" },
3097
- { parameter: import_format6.StandardParameter.MouthForm, value: 1, blend: "add" },
3098
- { parameter: import_format6.StandardParameter.MouthOpen, value: 0.8, blend: "overwrite" },
3205
+ { parameter: import_format7.StandardParameter.EyeOpenLeft, value: 0, blend: "multiply" },
3206
+ { parameter: import_format7.StandardParameter.EyeOpenRight, value: 0, blend: "multiply" },
3207
+ { parameter: import_format7.StandardParameter.MouthForm, value: 1, blend: "add" },
3208
+ { parameter: import_format7.StandardParameter.MouthOpen, value: 0.8, blend: "overwrite" },
3099
3209
  ...browsY(0.4),
3100
3210
  cheek(0.65)
3101
3211
  ]
@@ -3107,7 +3217,7 @@ var EXPRESSIONS = [
3107
3217
  parameters: [
3108
3218
  ...browsY(-0.5),
3109
3219
  ...browsTilt(0.8),
3110
- { parameter: import_format6.StandardParameter.MouthForm, value: -0.9, blend: "add" }
3220
+ { parameter: import_format7.StandardParameter.MouthForm, value: -0.9, blend: "add" }
3111
3221
  ]
3112
3222
  },
3113
3223
  {
@@ -3117,7 +3227,7 @@ var EXPRESSIONS = [
3117
3227
  parameters: [
3118
3228
  ...browsY(0.35),
3119
3229
  ...browsTilt(-0.8),
3120
- { parameter: import_format6.StandardParameter.MouthForm, value: -0.8, blend: "add" }
3230
+ { parameter: import_format7.StandardParameter.MouthForm, value: -0.8, blend: "add" }
3121
3231
  ]
3122
3232
  },
3123
3233
  {
@@ -3125,18 +3235,18 @@ var EXPRESSIONS = [
3125
3235
  description: "Surprised, mouth dropped open: startled, amazed. For shock, sudden news or disbelief.",
3126
3236
  parameters: [
3127
3237
  ...browsY(0.8),
3128
- { parameter: import_format6.StandardParameter.MouthOpen, value: 0.9, blend: "overwrite" }
3238
+ { parameter: import_format7.StandardParameter.MouthOpen, value: 0.9, blend: "overwrite" }
3129
3239
  ]
3130
3240
  },
3131
3241
  {
3132
3242
  id: "shy",
3133
3243
  description: "Shy or embarrassed: bashful, flustered. For being praised, teased or caught off guard.",
3134
- needs: [import_format6.StandardParameter.Cheek],
3244
+ needs: [import_format7.StandardParameter.Cheek],
3135
3245
  parameters: [
3136
3246
  cheek(1),
3137
3247
  // Gaze down.
3138
- { parameter: import_format6.StandardParameter.EyeballY, value: -0.65, blend: "add" },
3139
- { parameter: import_format6.StandardParameter.MouthForm, value: 0.4, blend: "add" },
3248
+ { parameter: import_format7.StandardParameter.EyeballY, value: -0.65, blend: "add" },
3249
+ { parameter: import_format7.StandardParameter.MouthForm, value: 0.4, blend: "add" },
3140
3250
  ...browsTilt(-0.25)
3141
3251
  ]
3142
3252
  }
@@ -3149,14 +3259,14 @@ function defaultExpressions(declared) {
3149
3259
  parameters: e.parameters.filter((t) => declared.has(t.parameter))
3150
3260
  }));
3151
3261
  }
3152
- var DEFAULT_MOTIONS = {
3262
+ var HEAD_MOTIONS = {
3153
3263
  Nod: [
3154
3264
  {
3155
3265
  description: "Nods yes: agreement, acknowledgement, understanding.",
3156
3266
  duration: 1,
3157
3267
  curves: [
3158
3268
  {
3159
- parameter: import_format6.StandardParameter.AngleY,
3269
+ parameter: import_format7.StandardParameter.AngleY,
3160
3270
  keys: [
3161
3271
  [0, 0],
3162
3272
  [0.3, -18],
@@ -3177,7 +3287,7 @@ var DEFAULT_MOTIONS = {
3177
3287
  fadeIn: 0.15,
3178
3288
  curves: [
3179
3289
  {
3180
- parameter: import_format6.StandardParameter.AngleX,
3290
+ parameter: import_format7.StandardParameter.AngleX,
3181
3291
  keys: [
3182
3292
  [0, 0],
3183
3293
  [0.2, -18],
@@ -3196,7 +3306,7 @@ var DEFAULT_MOTIONS = {
3196
3306
  duration: 1.4,
3197
3307
  curves: [
3198
3308
  {
3199
- parameter: import_format6.StandardParameter.AngleZ,
3309
+ parameter: import_format7.StandardParameter.AngleZ,
3200
3310
  keys: [
3201
3311
  [0, 0],
3202
3312
  [0.35, 20],
@@ -3208,6 +3318,55 @@ var DEFAULT_MOTIONS = {
3208
3318
  }
3209
3319
  ]
3210
3320
  };
3321
+ var WAVE = {
3322
+ Wave: [
3323
+ {
3324
+ description: "Waves hello with the right hand: greeting, goodbye, getting attention.",
3325
+ duration: 2.2,
3326
+ fadeIn: 0.15,
3327
+ fadeOut: 0.15,
3328
+ curves: [
3329
+ {
3330
+ parameter: import_format7.StandardParameter.ArmPoseRight,
3331
+ interpolation: "linear",
3332
+ keys: [
3333
+ [0, 0],
3334
+ [0.15, 1],
3335
+ [2.05, 1],
3336
+ [2.2, 0]
3337
+ ]
3338
+ },
3339
+ {
3340
+ parameter: import_format7.StandardParameter.ArmRight,
3341
+ keys: [
3342
+ [0, 0],
3343
+ [0.4, 10],
3344
+ [1.8, 10],
3345
+ [2.2, 0]
3346
+ ]
3347
+ },
3348
+ {
3349
+ parameter: import_format7.StandardParameter.ArmPoseAngleRight,
3350
+ keys: [
3351
+ [0, 0],
3352
+ [0.5, 0],
3353
+ [0.7, 12],
3354
+ [1, -12],
3355
+ [1.3, 12],
3356
+ [1.7, 0],
3357
+ [2.2, 0]
3358
+ ]
3359
+ }
3360
+ ]
3361
+ }
3362
+ ]
3363
+ };
3364
+ function defaultMotions(declared) {
3365
+ const waves = [import_format7.StandardParameter.ArmRight, import_format7.StandardParameter.ArmPoseRight, import_format7.StandardParameter.ArmPoseAngleRight].every(
3366
+ (id) => declared.has(id)
3367
+ );
3368
+ return waves ? { ...HEAD_MOTIONS, ...WAVE } : HEAD_MOTIONS;
3369
+ }
3211
3370
 
3212
3371
  // src/auto-rig/checks.ts
3213
3372
  var finite = (v) => typeof v === "number" && Number.isFinite(v);
@@ -3313,6 +3472,7 @@ function checkLayers(layers, canvas) {
3313
3472
  throw new Error(`auto-rig: missing required role(s) ${missing.join(", ")}`);
3314
3473
  }
3315
3474
  checkArmsHaveBody(seen);
3475
+ checkPosesHaveArms(seen);
3316
3476
  }
3317
3477
  function checkHeadEdges(edges, byRole) {
3318
3478
  if (edges === void 0) return;
@@ -3430,7 +3590,7 @@ function checkStrandEdges(strand, irises, hair) {
3430
3590
  }
3431
3591
 
3432
3592
  // src/auto-rig/context.ts
3433
- var import_format7 = require("@ikijs/format");
3593
+ var import_format8 = require("@ikijs/format");
3434
3594
 
3435
3595
  // src/auto-rig/head.ts
3436
3596
  function outerEdge(runs, axisX, side) {
@@ -4462,6 +4622,7 @@ function familyField(m, family) {
4462
4622
  return hairField(m, m.hairBack, NOD.hairBack);
4463
4623
  case "body":
4464
4624
  case "arm":
4625
+ case "forearm_pose":
4465
4626
  return () => [0, 0];
4466
4627
  }
4467
4628
  }
@@ -4561,7 +4722,7 @@ function renderedLander(turn, parts, grids, from = /* @__PURE__ */ new Map()) {
4561
4722
  const family = roleSpec(role).family;
4562
4723
  const part = parts.find((p) => p.id === role);
4563
4724
  const g = grids.get(family);
4564
- const warp = part?.warps?.find((w) => w.parameter === import_format7.StandardParameter.AngleX);
4725
+ const warp = part?.warps?.find((w) => w.parameter === import_format8.StandardParameter.AngleX);
4565
4726
  if (part === void 0 || part.mesh === void 0 || !g && !warp) {
4566
4727
  return x + field(role)(x, y, ax, 0)[0];
4567
4728
  }
@@ -5297,6 +5458,7 @@ var WARP_ID = {
5297
5458
  };
5298
5459
  var isGridFamily = (f) => f in WARP_ID;
5299
5460
  var isArm = (spec) => spec.family === "arm";
5461
+ var isPose = (spec) => spec.family === "forearm_pose";
5300
5462
  var GRID_PAD = 6;
5301
5463
  function generateIkiFromLayerSet(layers, canvas, options = {}) {
5302
5464
  checkLayers(layers, canvas);
@@ -5365,11 +5527,22 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
5365
5527
  const layer = byRole.get(spec.role);
5366
5528
  if (layer === void 0) continue;
5367
5529
  if (isArm(spec)) {
5530
+ const pose = POSE_OF_ARM[spec.role];
5368
5531
  parts.push(
5369
- ...armParts(spec.role, layer, arms.get(spec.role), parts.length)
5532
+ ...armParts(
5533
+ spec.role,
5534
+ layer,
5535
+ arms.get(spec.role),
5536
+ parts.length,
5537
+ has(pose) ? POSE_PARAMS[pose].pose : void 0
5538
+ )
5370
5539
  );
5371
5540
  continue;
5372
5541
  }
5542
+ if (isPose(spec)) {
5543
+ parts.push(forearmPosePart(spec.role, layer, parts.length));
5544
+ continue;
5545
+ }
5373
5546
  const built = buildPart(
5374
5547
  spec,
5375
5548
  boxOfLayer(layer),
@@ -5422,7 +5595,7 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
5422
5595
  part.mesh = bodyWarp.mesh;
5423
5596
  continue;
5424
5597
  }
5425
- if (family === "arm") continue;
5598
+ if (family === "arm" || family === "forearm_pose") continue;
5426
5599
  part.deformer = "headDeformer";
5427
5600
  if (part.mesh === void 0) continue;
5428
5601
  const own = familyField(turn, family);
@@ -5434,17 +5607,17 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
5434
5607
  }
5435
5608
  const parameters = declareParameters(new Set(byRole.keys()));
5436
5609
  const model = {
5437
- version: import_format8.IKI_FORMAT_VERSION,
5610
+ version: import_format9.IKI_FORMAT_VERSION,
5438
5611
  name: "auto-rigged",
5439
5612
  canvas: { width: canvas.width, height: canvas.height },
5440
5613
  parameters,
5441
5614
  parts,
5442
- deformers: deformers(frame, grids, bodyWarp?.deformer, arms),
5615
+ deformers: deformers(frame, grids, bodyWarp?.deformer, arms, byRole),
5443
5616
  ...has("hair_front") ? { physics: hairPhysics() } : {},
5444
5617
  expressions: defaultExpressions(new Set(parameters.map((p) => p.id))),
5445
- motions: DEFAULT_MOTIONS
5618
+ motions: defaultMotions(new Set(parameters.map((p) => p.id)))
5446
5619
  };
5447
- const valid = (0, import_format8.parseIkiModel)(model);
5620
+ const valid = (0, import_format9.parseIkiModel)(model);
5448
5621
  if (hasNose && options.onTurnSolved) {
5449
5622
  options.onTurnSolved(
5450
5623
  turnReport(
@@ -5459,14 +5632,14 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
5459
5632
  function turnWarps(mesh, b, fieldOf) {
5460
5633
  return [
5461
5634
  localWarp(
5462
- import_format8.StandardParameter.AngleX,
5635
+ import_format9.StandardParameter.AngleX,
5463
5636
  mesh,
5464
5637
  b,
5465
5638
  X_STOPS,
5466
5639
  ([x, y], v, i) => v === 0 ? [0, 0] : [fieldOf(i)(x, y, v, 0)[0], 0]
5467
5640
  ),
5468
5641
  localWarp(
5469
- import_format8.StandardParameter.AngleY,
5642
+ import_format9.StandardParameter.AngleY,
5470
5643
  mesh,
5471
5644
  b,
5472
5645
  Y_STOPS,
@@ -5514,7 +5687,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5514
5687
  }
5515
5688
  if (mesh !== void 0) part.mesh = mesh;
5516
5689
  const side = spec.role.endsWith("_L") ? "L" : "R";
5517
- const eyeOpen = side === "L" ? import_format8.StandardParameter.EyeOpenLeft : import_format8.StandardParameter.EyeOpenRight;
5690
+ const eyeOpen = side === "L" ? import_format9.StandardParameter.EyeOpenLeft : import_format9.StandardParameter.EyeOpenRight;
5518
5691
  const hh = frame.hh;
5519
5692
  switch (spec.role) {
5520
5693
  case "face":
@@ -5529,7 +5702,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5529
5702
  headOwnRoll(byRole.has("body"))
5530
5703
  ),
5531
5704
  localWarp(
5532
- import_format8.StandardParameter.Breath,
5705
+ import_format9.StandardParameter.Breath,
5533
5706
  mesh,
5534
5707
  b,
5535
5708
  [0, 1],
@@ -5576,8 +5749,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5576
5749
  if (byRole.has("mouth_open")) {
5577
5750
  warps.push(mouthWiden(mesh, b));
5578
5751
  bindings.push(
5579
- { parameter: import_format8.StandardParameter.MouthOpen, channel: "opacity", from: 1, to: 0 },
5580
- { parameter: import_format8.StandardParameter.MouthOpen, channel: "opacity", from: 1, to: 0 }
5752
+ { parameter: import_format9.StandardParameter.MouthOpen, channel: "opacity", from: 1, to: 0 },
5753
+ { parameter: import_format9.StandardParameter.MouthOpen, channel: "opacity", from: 1, to: 0 }
5581
5754
  );
5582
5755
  extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
5583
5756
  } else {
@@ -5587,19 +5760,19 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5587
5760
  );
5588
5761
  bindings.push(
5589
5762
  {
5590
- parameter: import_format8.StandardParameter.MouthOpen,
5763
+ parameter: import_format9.StandardParameter.MouthOpen,
5591
5764
  channel: "scaleY",
5592
5765
  from: 0,
5593
5766
  to: roundTo(grow, 0.01)
5594
5767
  },
5595
5768
  {
5596
- parameter: import_format8.StandardParameter.MouthOpen,
5769
+ parameter: import_format9.StandardParameter.MouthOpen,
5597
5770
  channel: "scaleX",
5598
5771
  from: 0,
5599
5772
  to: roundTo(AMPLITUDE.mouthOpenWidth - 1, 0.01)
5600
5773
  },
5601
5774
  {
5602
- parameter: import_format8.StandardParameter.MouthOpen,
5775
+ parameter: import_format9.StandardParameter.MouthOpen,
5603
5776
  channel: "translateY",
5604
5777
  from: 0,
5605
5778
  to: -roundTo(grow * bh(b) / 2, 0.01)
@@ -5611,7 +5784,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5611
5784
  case "mouth_open":
5612
5785
  warps.push(mouthForm(mesh, b), mouthOpenGrow(mesh, b));
5613
5786
  bindings.push({
5614
- parameter: import_format8.StandardParameter.MouthOpen,
5787
+ parameter: import_format9.StandardParameter.MouthOpen,
5615
5788
  channel: "opacity",
5616
5789
  from: 0,
5617
5790
  to: 1
@@ -5621,7 +5794,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5621
5794
  case "blush_L":
5622
5795
  case "blush_R":
5623
5796
  bindings.push({
5624
- parameter: import_format8.StandardParameter.Cheek,
5797
+ parameter: import_format9.StandardParameter.Cheek,
5625
5798
  channel: "opacity",
5626
5799
  from: AMPLITUDE.blushRest,
5627
5800
  to: 1
@@ -5632,8 +5805,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5632
5805
  if (byRole.has("hair_front")) {
5633
5806
  const amp = style.sway * AMPLITUDE.sway * hh;
5634
5807
  warps.push(
5635
- hairSway(import_format8.StandardParameter.HairSwayX, mesh, b, amp),
5636
- hairSway(import_format8.StandardParameter.HairSwayZ, mesh, b, amp)
5808
+ hairSway(import_format9.StandardParameter.HairSwayX, mesh, b, amp),
5809
+ hairSway(import_format9.StandardParameter.HairSwayZ, mesh, b, amp)
5637
5810
  );
5638
5811
  }
5639
5812
  warps.push(tiltHang(mesh, b, pivot, hairHangWeight(frame.chinY, b.y0)));
@@ -5706,10 +5879,14 @@ function meshScale(role, frame) {
5706
5879
  return Infinity;
5707
5880
  }
5708
5881
  }
5709
- function deformers(frame, grids, bodyWarp, arms) {
5882
+ function deformers(frame, grids, bodyWarp, arms, byRole) {
5710
5883
  const out = [];
5711
5884
  if (bodyWarp !== void 0) out.push(bodyWarp);
5712
5885
  for (const [role, g] of arms) out.push(...armDeformers(role, g));
5886
+ for (const [role, arm] of arms) {
5887
+ const pose = POSE_OF_ARM[role];
5888
+ if (byRole.has(pose)) out.push(forearmPoseDeformer(pose, arm));
5889
+ }
5713
5890
  const hasBody = bodyWarp !== void 0;
5714
5891
  const roll = headOwnRoll(hasBody);
5715
5892
  out.push({
@@ -5718,9 +5895,9 @@ function deformers(frame, grids, bodyWarp, arms) {
5718
5895
  pivot: { x: roundTo(frame.axisX, 0.01), y: roundTo(frame.chinY, 0.01) },
5719
5896
  bindings: [
5720
5897
  // AngleZ is clockwise-positive (Live2D's); a rotation is CCW-positive.
5721
- { parameter: import_format8.StandardParameter.AngleZ, channel: "rotate", from: roll, to: -roll },
5898
+ { parameter: import_format9.StandardParameter.AngleZ, channel: "rotate", from: roll, to: -roll },
5722
5899
  {
5723
- parameter: import_format8.StandardParameter.Breath,
5900
+ parameter: import_format9.StandardParameter.Breath,
5724
5901
  channel: "translateY",
5725
5902
  from: 0,
5726
5903
  to: roundTo(headOwnBreath(hasBody) * frame.hh, 0.01)
@@ -5738,8 +5915,8 @@ function deformers(frame, grids, bodyWarp, arms) {
5738
5915
  points: g.lattice.points
5739
5916
  },
5740
5917
  warp2d: {
5741
- parameter: import_format8.StandardParameter.AngleX,
5742
- parameterY: import_format8.StandardParameter.AngleY,
5918
+ parameter: import_format9.StandardParameter.AngleX,
5919
+ parameterY: import_format9.StandardParameter.AngleY,
5743
5920
  valuesX: X_STOPS,
5744
5921
  valuesY: Y_STOPS,
5745
5922
  keyforms2d: g.keyforms.map((offsets) => ({ offsets }))
@@ -5752,16 +5929,16 @@ function hairPhysics() {
5752
5929
  return [
5753
5930
  {
5754
5931
  id: "hairSway",
5755
- input: { parameter: import_format8.StandardParameter.AngleX, weight: 1 },
5756
- output: { parameter: import_format8.StandardParameter.HairSwayX, scale: 5 },
5932
+ input: { parameter: import_format9.StandardParameter.AngleX, weight: 1 },
5933
+ output: { parameter: import_format9.StandardParameter.HairSwayX, scale: 5 },
5757
5934
  mass: 1,
5758
5935
  stiffness: 30,
5759
5936
  damping: 3
5760
5937
  },
5761
5938
  {
5762
5939
  id: "hairTilt",
5763
- input: { parameter: import_format8.StandardParameter.AngleZ, weight: 1 },
5764
- output: { parameter: import_format8.StandardParameter.HairSwayZ, scale: 8 },
5940
+ input: { parameter: import_format9.StandardParameter.AngleZ, weight: 1 },
5941
+ output: { parameter: import_format9.StandardParameter.HairSwayZ, scale: 8 },
5765
5942
  mass: 1,
5766
5943
  stiffness: 25,
5767
5944
  damping: 2.5
@@ -5771,47 +5948,55 @@ function hairPhysics() {
5771
5948
  function declareParameters(roles) {
5772
5949
  const out = [];
5773
5950
  const add = (id, name, min, max, def) => out.push({ id, name, min, max, default: def });
5774
- add(import_format8.StandardParameter.AngleX, "Head Angle", -30, 30, 0);
5775
- add(import_format8.StandardParameter.AngleY, "Head Angle Y", -30, 30, 0);
5776
- add(import_format8.StandardParameter.AngleZ, "Head Angle Z", -30, 30, 0);
5951
+ add(import_format9.StandardParameter.AngleX, "Head Angle", -30, 30, 0);
5952
+ add(import_format9.StandardParameter.AngleY, "Head Angle Y", -30, 30, 0);
5953
+ add(import_format9.StandardParameter.AngleZ, "Head Angle Z", -30, 30, 0);
5777
5954
  if (roles.has("body")) {
5778
- add(import_format8.StandardParameter.BodyAngleX, "Body Angle X", -10, 10, 0);
5779
- add(import_format8.StandardParameter.BodyAngleY, "Body Angle Y", -10, 10, 0);
5780
- add(import_format8.StandardParameter.BodyAngleZ, "Body Angle Z", -10, 10, 0);
5955
+ add(import_format9.StandardParameter.BodyAngleX, "Body Angle X", -10, 10, 0);
5956
+ add(import_format9.StandardParameter.BodyAngleY, "Body Angle Y", -10, 10, 0);
5957
+ add(import_format9.StandardParameter.BodyAngleZ, "Body Angle Z", -10, 10, 0);
5781
5958
  }
5959
+ const addPose = (side, pose, angle) => {
5960
+ add(pose, `Arm Pose ${side}`, 0, 1, 0);
5961
+ add(angle, `Arm Pose Angle ${side}`, ...POSE_ANGLE_RANGE, 0);
5962
+ };
5782
5963
  const [armMin, armMax] = ARM_RANGE;
5783
5964
  const [elbowMin, elbowMax] = ELBOW_RANGE;
5784
5965
  if (roles.has("arm_L")) {
5785
- add(import_format8.StandardParameter.ArmLeft, "Arm L", armMin, armMax, 0);
5786
- add(import_format8.StandardParameter.ElbowLeft, "Elbow L", elbowMin, elbowMax, 0);
5966
+ add(import_format9.StandardParameter.ArmLeft, "Arm L", armMin, armMax, 0);
5967
+ add(import_format9.StandardParameter.ElbowLeft, "Elbow L", elbowMin, elbowMax, 0);
5968
+ if (roles.has("forearm_pose_L"))
5969
+ addPose("L", import_format9.StandardParameter.ArmPoseLeft, import_format9.StandardParameter.ArmPoseAngleLeft);
5787
5970
  }
5788
5971
  if (roles.has("arm_R")) {
5789
- add(import_format8.StandardParameter.ArmRight, "Arm R", armMin, armMax, 0);
5790
- add(import_format8.StandardParameter.ElbowRight, "Elbow R", elbowMin, elbowMax, 0);
5972
+ add(import_format9.StandardParameter.ArmRight, "Arm R", armMin, armMax, 0);
5973
+ add(import_format9.StandardParameter.ElbowRight, "Elbow R", elbowMin, elbowMax, 0);
5974
+ if (roles.has("forearm_pose_R"))
5975
+ addPose("R", import_format9.StandardParameter.ArmPoseRight, import_format9.StandardParameter.ArmPoseAngleRight);
5791
5976
  }
5792
- add(import_format8.StandardParameter.EyeOpenLeft, "Eye L", 0, 1, 1);
5793
- add(import_format8.StandardParameter.EyeOpenRight, "Eye R", 0, 1, 1);
5977
+ add(import_format9.StandardParameter.EyeOpenLeft, "Eye L", 0, 1, 1);
5978
+ add(import_format9.StandardParameter.EyeOpenRight, "Eye R", 0, 1, 1);
5794
5979
  if ([...roles].some((r) => /^(iris|pupil|highlight)_/.test(r))) {
5795
- add(import_format8.StandardParameter.EyeballX, "Gaze X", -1, 1, 0);
5796
- add(import_format8.StandardParameter.EyeballY, "Gaze Y", -1, 1, 0);
5980
+ add(import_format9.StandardParameter.EyeballX, "Gaze X", -1, 1, 0);
5981
+ add(import_format9.StandardParameter.EyeballY, "Gaze Y", -1, 1, 0);
5797
5982
  }
5798
- add(import_format8.StandardParameter.MouthOpen, "Mouth Open", 0, 1, 0);
5799
- add(import_format8.StandardParameter.MouthForm, "Mouth Form", -1, 1, 0);
5983
+ add(import_format9.StandardParameter.MouthOpen, "Mouth Open", 0, 1, 0);
5984
+ add(import_format9.StandardParameter.MouthForm, "Mouth Form", -1, 1, 0);
5800
5985
  if (roles.has("brow_L")) {
5801
- add(import_format8.StandardParameter.BrowLeftY, "Brow L Y", -1, 1, 0);
5802
- add(import_format8.StandardParameter.BrowLeftAngle, "Brow L Angle", -1, 1, 0);
5986
+ add(import_format9.StandardParameter.BrowLeftY, "Brow L Y", -1, 1, 0);
5987
+ add(import_format9.StandardParameter.BrowLeftAngle, "Brow L Angle", -1, 1, 0);
5803
5988
  }
5804
5989
  if (roles.has("brow_R")) {
5805
- add(import_format8.StandardParameter.BrowRightY, "Brow R Y", -1, 1, 0);
5806
- add(import_format8.StandardParameter.BrowRightAngle, "Brow R Angle", -1, 1, 0);
5990
+ add(import_format9.StandardParameter.BrowRightY, "Brow R Y", -1, 1, 0);
5991
+ add(import_format9.StandardParameter.BrowRightAngle, "Brow R Angle", -1, 1, 0);
5807
5992
  }
5808
5993
  if (roles.has("blush_L") || roles.has("blush_R")) {
5809
- add(import_format8.StandardParameter.Cheek, "Cheek", 0, 1, 0);
5994
+ add(import_format9.StandardParameter.Cheek, "Cheek", 0, 1, 0);
5810
5995
  }
5811
- add(import_format8.StandardParameter.Breath, "Breath", 0, 1, 0);
5996
+ add(import_format9.StandardParameter.Breath, "Breath", 0, 1, 0);
5812
5997
  if (roles.has("hair_front")) {
5813
- add(import_format8.StandardParameter.HairSwayX, "Hair Sway X", -SWAY_RANGE, SWAY_RANGE, 0);
5814
- add(import_format8.StandardParameter.HairSwayZ, "Hair Sway Z", -SWAY_RANGE, SWAY_RANGE, 0);
5998
+ add(import_format9.StandardParameter.HairSwayX, "Hair Sway X", -SWAY_RANGE, SWAY_RANGE, 0);
5999
+ add(import_format9.StandardParameter.HairSwayZ, "Hair Sway Z", -SWAY_RANGE, SWAY_RANGE, 0);
5815
6000
  }
5816
6001
  return out;
5817
6002
  }
@@ -5859,6 +6044,7 @@ function declareParameters(roles) {
5859
6044
  createLayerSetMeasurer,
5860
6045
  denseCoreOf,
5861
6046
  detectAlphaBbox,
6047
+ forearmPoseGeometry,
5862
6048
  foregroundSpan,
5863
6049
  generateIkiFromLayerSet,
5864
6050
  headHalfOf,