@ikijs/editor 0.16.1 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -33,6 +33,7 @@ __export(index_exports, {
33
33
  DeletePhysicsRig: () => DeletePhysicsRig,
34
34
  EditorDocument: () => EditorDocument,
35
35
  HEAD_BAND: () => HEAD_BAND,
36
+ LayerGeometryError: () => LayerGeometryError,
36
37
  SPECK_CORE_FRACTION: () => SPECK_CORE_FRACTION,
37
38
  SetDeformerBindings: () => SetDeformerBindings,
38
39
  SetDeformerParent: () => SetDeformerParent,
@@ -67,6 +68,7 @@ __export(index_exports, {
67
68
  isSpeckCore: () => isSpeckCore,
68
69
  packAtlas: () => packAtlas,
69
70
  parseLayerRoles: () => parseLayerRoles,
71
+ partIdsOfRole: () => partIdsOfRole,
70
72
  upsertGridKeyform: () => upsertGridKeyform,
71
73
  uvRectFor: () => uvRectFor,
72
74
  validateDeformerDelete: () => validateDeformerDelete,
@@ -1632,6 +1634,14 @@ function facePlateHalfOf(layers) {
1632
1634
  return face.cropW / 2;
1633
1635
  }
1634
1636
  var STRAND_MIN_RUN_FRACTION = 0.5;
1637
+ var ROW_RUN_ROLES = /* @__PURE__ */ new Set([
1638
+ "face",
1639
+ "hair_front",
1640
+ "hair_back",
1641
+ "body",
1642
+ "arm_L",
1643
+ "arm_R"
1644
+ ]);
1635
1645
  function createLayerSetMeasurer(canvas) {
1636
1646
  for (const field of ["width", "height"]) {
1637
1647
  const value = canvas[field];
@@ -1661,7 +1671,7 @@ function createLayerSetMeasurer(canvas) {
1661
1671
  if (bbox === null) return null;
1662
1672
  const rowLeft = new Int32Array(canvasH).fill(canvasW);
1663
1673
  const rowRight = new Int32Array(canvasH).fill(-1);
1664
- const rowRuns = role === "face" || role === "hair_front" || role === "hair_back" ? [] : void 0;
1674
+ const rowRuns = ROW_RUN_ROLES.has(role) ? [] : void 0;
1665
1675
  for (let y = 0; y < canvasH; y++) {
1666
1676
  let runs;
1667
1677
  if (rowRuns !== void 0 && y >= bbox.y && y < bbox.y + bbox.h) {
@@ -2003,7 +2013,13 @@ var PROFILE_VALUES = {
2003
2013
  brow: 0.15,
2004
2014
  breath: 0.03,
2005
2015
  sway: 0.08,
2006
- blushRest: 0.4
2016
+ blushRest: 0.4,
2017
+ bodySlide: 0.1,
2018
+ bodyNarrow: 0.05,
2019
+ bodyBow: 0.06,
2020
+ bodyRoll: 4,
2021
+ bodyFollowX: 0.3,
2022
+ bodyFollowZ: 0.3
2007
2023
  };
2008
2024
  function deriveProfile(v) {
2009
2025
  const farLead = v.lead * (1 - v.fore / 2);
@@ -2097,9 +2113,32 @@ function deriveProfile(v) {
2097
2113
  * slide, to the next 0.05. */
2098
2114
  hiddenNeck: Math.ceil(30 * (v.slide + v.lead) - 1e-9) / 20
2099
2115
  };
2100
- return { TURN: turn, NOD: nod, ROLL_DEG: v.rollDeg, AMPLITUDE: amplitude };
2116
+ const body = {
2117
+ /** BodyAngleX: the upper body's slide, hh. */
2118
+ slide: v.bodySlide,
2119
+ /** BodyAngleX: how much narrower the upper body gets, either way. */
2120
+ narrow: v.bodyNarrow,
2121
+ /** BodyAngleY: the upper body's rise (+) or bow (−), hh. */
2122
+ bow: v.bodyBow,
2123
+ /** BodyAngleZ: the upper body's roll about the hips, degrees. */
2124
+ rollDeg: v.bodyRoll,
2125
+ /** AngleX: the body's follow, as a share of BodyAngleX's motion. */
2126
+ followX: v.bodyFollowX,
2127
+ /** AngleZ: the body's follow, as a share of its roll. */
2128
+ followZ: v.bodyFollowZ,
2129
+ /** The roll the follow gives the body at AngleZ ±30, degrees: β, the
2130
+ * share of `ROLL_DEG` the head's own roll leaves to the body. */
2131
+ followRoll: v.bodyFollowZ * v.bodyRoll
2132
+ };
2133
+ return {
2134
+ TURN: turn,
2135
+ NOD: nod,
2136
+ ROLL_DEG: v.rollDeg,
2137
+ AMPLITUDE: amplitude,
2138
+ BODY: body
2139
+ };
2101
2140
  }
2102
- var { TURN, NOD, ROLL_DEG, AMPLITUDE } = deriveProfile(PROFILE_VALUES);
2141
+ var { TURN, NOD, ROLL_DEG, AMPLITUDE, BODY } = deriveProfile(PROFILE_VALUES);
2103
2142
  var STYLE_KNOBS = [
2104
2143
  "turn",
2105
2144
  "featureLead",
@@ -2129,6 +2168,12 @@ var TurnTargetError = class extends Error {
2129
2168
  this.name = "TurnTargetError";
2130
2169
  }
2131
2170
  };
2171
+ var LayerGeometryError = class extends Error {
2172
+ constructor(message) {
2173
+ super(message);
2174
+ this.name = "LayerGeometryError";
2175
+ }
2176
+ };
2132
2177
  var DEFAULT_TURN_TARGETS = Object.freeze({
2133
2178
  eyeShift: round2((TURN.eyeFar + TURN.eyeNear) / 2 - TURN.hairBack),
2134
2179
  farEyeRatio: round2(TURN.eyeFarScale / TURN.eyeNearScale),
@@ -2139,6 +2184,8 @@ var DEFAULT_TURN_TARGETS = Object.freeze({
2139
2184
  var ROLE_TABLE = [
2140
2185
  { role: "hair_back", family: "hair_back" },
2141
2186
  { role: "body", family: "body" },
2187
+ { role: "arm_L", family: "arm", parts: ["arm_L", "forearm_L"] },
2188
+ { role: "arm_R", family: "arm", parts: ["arm_R", "forearm_R"] },
2142
2189
  { role: "face", family: "face", required: true },
2143
2190
  { role: "blush_L", family: "face" },
2144
2191
  { role: "blush_R", family: "face" },
@@ -2170,6 +2217,15 @@ function roleSpec(role) {
2170
2217
  if (spec === void 0) throw new Error(`auto-rig: unknown role "${role}"`);
2171
2218
  return spec;
2172
2219
  }
2220
+ function partIdsOfRole(role) {
2221
+ return roleSpec(role).parts ?? [role];
2222
+ }
2223
+ var ROLE_BY_PART = new Map(
2224
+ ROLE_TABLE.flatMap((r) => (r.parts ?? []).map((p) => [p, r.role]))
2225
+ );
2226
+ function roleOfPart(partId) {
2227
+ return ROLE_BY_PART.get(partId) ?? partId;
2228
+ }
2173
2229
  var ALIASES = {
2174
2230
  eyebrow_L: "brow_L",
2175
2231
  eyebrow_R: "brow_R",
@@ -2180,6 +2236,16 @@ function roleOf(fileName) {
2180
2236
  const role = fileName.replace(/\.[^.]+$/, "").toLowerCase().replace(/[-\s]+/g, "_").replace(/_([lr])$/, (_, s) => `_${s.toUpperCase()}`);
2181
2237
  return ALIASES[role] ?? role;
2182
2238
  }
2239
+ function checkArmsHaveBody(roles) {
2240
+ const has = new Set(roles);
2241
+ if (has.has("body")) return;
2242
+ const arm = ROLE_TABLE.find((r) => r.family === "arm" && has.has(r.role));
2243
+ if (arm !== void 0) {
2244
+ throw new Error(
2245
+ `auto-rig: ${arm.role} needs a body layer (an arm hangs from the body's shoulder)`
2246
+ );
2247
+ }
2248
+ }
2183
2249
  function parseLayerRoles(fileNames) {
2184
2250
  const seen = /* @__PURE__ */ new Map();
2185
2251
  const out = [];
@@ -2205,11 +2271,12 @@ function parseLayerRoles(fileNames) {
2205
2271
  `auto-rig: missing required role${missing.length > 1 ? "s" : ""} ${missing.join(", ")}`
2206
2272
  );
2207
2273
  }
2274
+ checkArmsHaveBody(seen.keys());
2208
2275
  return out;
2209
2276
  }
2210
2277
 
2211
2278
  // src/auto-rig/generate.ts
2212
- var import_format6 = require("@ikijs/format");
2279
+ var import_format8 = require("@ikijs/format");
2213
2280
 
2214
2281
  // src/auto-rig/animations.ts
2215
2282
  var import_format3 = require("@ikijs/format");
@@ -2360,6 +2427,12 @@ var DEFAULT_MOTIONS = {
2360
2427
  ]
2361
2428
  };
2362
2429
 
2430
+ // src/auto-rig/arms.ts
2431
+ var import_format6 = require("@ikijs/format");
2432
+
2433
+ // src/auto-rig/body.ts
2434
+ var import_format5 = require("@ikijs/format");
2435
+
2363
2436
  // src/auto-rig/layout.ts
2364
2437
  function boxOfLayer(layer) {
2365
2438
  return boxOfPixels(layer.bbox, layer.canvasW, layer.canvasH);
@@ -2433,6 +2506,651 @@ function meshPoints(mesh, box) {
2433
2506
  return out;
2434
2507
  }
2435
2508
 
2509
+ // src/auto-rig/drivers.ts
2510
+ var import_format4 = require("@ikijs/format");
2511
+ var SWAY_RANGE = 20;
2512
+ var SWAY_CURVE = 1.4;
2513
+ var HAIR_HANG = 0.35;
2514
+ var Z_STOPS = [-30, 0, 30];
2515
+ function localWarp(parameter, mesh, box, values, f) {
2516
+ const pts = meshPoints(mesh, box);
2517
+ const w = bw(box);
2518
+ const h = bh(box);
2519
+ return {
2520
+ parameter,
2521
+ keyforms: values.map((value) => ({
2522
+ value,
2523
+ offsets: pts.flatMap((p, i) => {
2524
+ const [dx, dy] = f(p, value, i);
2525
+ return [roundTo(dx / w, 1e-4), roundTo(dy / h, 1e-4)];
2526
+ })
2527
+ }))
2528
+ };
2529
+ }
2530
+ var rollOf = (v, deg = ROLL_DEG) => -v / 30 * deg * DEG;
2531
+ function motionReach(warps, mesh, box, extra = [0, 0]) {
2532
+ const out = {
2533
+ left: extra[0],
2534
+ right: extra[0],
2535
+ bottom: extra[1],
2536
+ top: extra[1]
2537
+ };
2538
+ if (mesh === void 0) return out;
2539
+ const pts = meshPoints(mesh, box);
2540
+ for (const warp of warps ?? []) {
2541
+ let l = 0;
2542
+ let r = 0;
2543
+ let b = 0;
2544
+ let t = 0;
2545
+ for (const k of warp.keyforms) {
2546
+ pts.forEach(([x, y], i) => {
2547
+ const px = x + k.offsets[2 * i] * bw(box);
2548
+ const py = y + k.offsets[2 * i + 1] * bh(box);
2549
+ l = Math.max(l, box.x0 - px);
2550
+ r = Math.max(r, px - box.x1);
2551
+ b = Math.max(b, box.y0 - py);
2552
+ t = Math.max(t, py - box.y1);
2553
+ });
2554
+ }
2555
+ out.left += l;
2556
+ out.right += r;
2557
+ out.bottom += b;
2558
+ out.top += t;
2559
+ }
2560
+ return out;
2561
+ }
2562
+ function blinkFold(param, mesh, box, travel, eye = box) {
2563
+ const yc = eye.y1 - travel * bh(eye);
2564
+ return localWarp(
2565
+ param,
2566
+ mesh,
2567
+ box,
2568
+ [0, 1],
2569
+ ([, y], v) => v === 1 ? [0, 0] : [0, yc - y]
2570
+ );
2571
+ }
2572
+ function lashFold(param, mesh, lash, eye, travel) {
2573
+ const yc = eye.y1 - travel * bh(eye) - 0.04 * bh(eye);
2574
+ const c = cx(lash);
2575
+ const half = bw(lash) / 2;
2576
+ const sag = 0.3 * bh(lash);
2577
+ return localWarp(param, mesh, lash, [0, 1], ([x, y], v) => {
2578
+ if (v === 1) return [0, 0];
2579
+ const u = (x - c) / half;
2580
+ return [0, yc + (y - lash.y0) * 0.5 - sag * (1 - u * u) - y];
2581
+ });
2582
+ }
2583
+ function gazeBindings(irisW, share = 1) {
2584
+ const gx = AMPLITUDE.gazeX * irisW * share;
2585
+ const gy = AMPLITUDE.gazeY * irisW * share;
2586
+ return [
2587
+ {
2588
+ parameter: import_format4.StandardParameter.EyeballX,
2589
+ channel: "translateX",
2590
+ from: -round1(gx),
2591
+ to: round1(gx)
2592
+ },
2593
+ {
2594
+ parameter: import_format4.StandardParameter.EyeballY,
2595
+ channel: "translateY",
2596
+ from: -round1(gy),
2597
+ to: round1(gy)
2598
+ }
2599
+ ];
2600
+ }
2601
+ function gazeReach(irisW, share = 1) {
2602
+ return [AMPLITUDE.gazeX * irisW * share, AMPLITUDE.gazeY * irisW * share];
2603
+ }
2604
+ function browReach(hh) {
2605
+ return AMPLITUDE.brow * hh;
2606
+ }
2607
+ function browBindings(side, reach) {
2608
+ const y = side === "L" ? import_format4.StandardParameter.BrowLeftY : import_format4.StandardParameter.BrowRightY;
2609
+ const a = side === "L" ? import_format4.StandardParameter.BrowLeftAngle : import_format4.StandardParameter.BrowRightAngle;
2610
+ return [
2611
+ {
2612
+ parameter: y,
2613
+ channel: "translateY",
2614
+ from: -round1(reach),
2615
+ to: round1(reach)
2616
+ },
2617
+ { parameter: a, channel: "rotate", from: -BROW_TILT, to: BROW_TILT }
2618
+ ];
2619
+ }
2620
+ var BROW_TILT = 15;
2621
+ function mouthForm(mesh, box) {
2622
+ const a = 0.3 * bh(box);
2623
+ const c = cx(box);
2624
+ const half = bw(box) / 2;
2625
+ return localWarp(import_format4.StandardParameter.MouthForm, mesh, box, [-1, 0, 1], ([x], v) => {
2626
+ if (v === 0) return [0, 0];
2627
+ const u = (x - c) / half;
2628
+ const widen = v > 0 ? 0.05 : 0.03;
2629
+ return [v * widen * (x - c), v * a * (u * u - 0.3)];
2630
+ });
2631
+ }
2632
+ function mouthOpenGrow(mesh, box) {
2633
+ const c = cx(box);
2634
+ return localWarp(
2635
+ import_format4.StandardParameter.MouthOpen,
2636
+ mesh,
2637
+ box,
2638
+ [0, 1],
2639
+ ([x, y], v) => v === 1 ? [(AMPLITUDE.mouthOpenWidth - 1) * (x - c), 0] : [0, box.y1 + (y - box.y1) * 0.2 - y]
2640
+ );
2641
+ }
2642
+ function mouthWiden(mesh, box) {
2643
+ const c = cx(box);
2644
+ return localWarp(import_format4.StandardParameter.MouthOpen, mesh, box, [0, 1], ([x], v) => [
2645
+ v * (AMPLITUDE.mouthOpenWidth - 1) * (x - c),
2646
+ 0
2647
+ ]);
2648
+ }
2649
+ function hairSway(param, mesh, box, amp) {
2650
+ return localWarp(
2651
+ param,
2652
+ mesh,
2653
+ box,
2654
+ [-SWAY_RANGE, 0, SWAY_RANGE],
2655
+ ([, y], v) => {
2656
+ const t = Math.max(0, (box.y1 - y) / bh(box));
2657
+ return [v / SWAY_RANGE * amp * t ** SWAY_CURVE, 0];
2658
+ }
2659
+ );
2660
+ }
2661
+ function tiltHang(mesh, box, pivot, weight, deg = ROLL_DEG) {
2662
+ return localWarp(import_format4.StandardParameter.AngleZ, mesh, box, Z_STOPS, ([x, y], v, i) => {
2663
+ const g = weight(y, i);
2664
+ if (g === 0 || v === 0) return [0, 0];
2665
+ const a = -g * rollOf(v, deg);
2666
+ const px = x - pivot[0];
2667
+ const py = y - pivot[1];
2668
+ return [
2669
+ pivot[0] + px * Math.cos(a) - py * Math.sin(a) - x,
2670
+ pivot[1] + px * Math.sin(a) + py * Math.cos(a) - y
2671
+ ];
2672
+ });
2673
+ }
2674
+ function hairHangWeight(pivotY, bottom) {
2675
+ if (bottom >= pivotY) return () => 0;
2676
+ return (y) => HAIR_HANG * smoothstep(pivotY, bottom, y);
2677
+ }
2678
+ var round1 = (v) => roundTo(v, 0.1);
2679
+
2680
+ // src/auto-rig/grid.ts
2681
+ var X_STOPS = [-30, 0, 30];
2682
+ var Y_STOPS = [-30, 0, 30];
2683
+ function lattice(box, cell, min, max) {
2684
+ const cols = cellsFor(box.x1 - box.x0, cell, min, max);
2685
+ const rows = cellsFor(box.y1 - box.y0, cell, min, max);
2686
+ const points = [];
2687
+ for (let r = 0; r <= rows; r++) {
2688
+ const y = box.y1 - (box.y1 - box.y0) * r / rows;
2689
+ for (let c = 0; c <= cols; c++) {
2690
+ points.push(
2691
+ roundTo(box.x0 + (box.x1 - box.x0) * c / cols, 0.01),
2692
+ roundTo(y, 0.01)
2693
+ );
2694
+ }
2695
+ }
2696
+ return { box, cols, rows, points };
2697
+ }
2698
+ function bake(l, field) {
2699
+ const out = [];
2700
+ for (const ay of Y_STOPS) {
2701
+ for (const ax of X_STOPS) {
2702
+ const offsets = [];
2703
+ for (let k = 0; k < l.points.length; k += 2) {
2704
+ if (ax === 0 && ay === 0) {
2705
+ offsets.push(0, 0);
2706
+ continue;
2707
+ }
2708
+ const [dx, dy] = field(l.points[k], l.points[k + 1], ax, ay);
2709
+ offsets.push(roundTo(dx, 0.1), roundTo(dy, 0.1));
2710
+ }
2711
+ out.push(offsets);
2712
+ }
2713
+ }
2714
+ return out;
2715
+ }
2716
+ function keyformAt(keyforms, ax) {
2717
+ const j = Y_STOPS.indexOf(0);
2718
+ const row = X_STOPS.map((_, i) => keyforms[j * X_STOPS.length + i]);
2719
+ return blendStops(X_STOPS, row, ax);
2720
+ }
2721
+ function blendStops(values, offsets, v) {
2722
+ const x = clamp(v, values[0], values[values.length - 1]);
2723
+ let i = 0;
2724
+ while (i < values.length - 2 && x > values[i + 1]) i++;
2725
+ const t = (x - values[i]) / (values[i + 1] - values[i]);
2726
+ return offsets[i].map((o, k) => o + (offsets[i + 1][k] - o) * t);
2727
+ }
2728
+ function land(l, offsets, x, y) {
2729
+ const { cols, rows, points } = l;
2730
+ const stride = cols + 1;
2731
+ let col = cols - 1;
2732
+ for (let c = 0; c < cols; c++) {
2733
+ if (x < points[(c + 1) * 2]) {
2734
+ col = c;
2735
+ break;
2736
+ }
2737
+ }
2738
+ let row = rows - 1;
2739
+ for (let r = 0; r < rows; r++) {
2740
+ if (y > points[(r + 1) * stride * 2 + 1]) {
2741
+ row = r;
2742
+ break;
2743
+ }
2744
+ }
2745
+ const x0 = points[col * 2];
2746
+ const x1 = points[(col + 1) * 2];
2747
+ const yT = points[row * stride * 2 + 1];
2748
+ const yB = points[(row + 1) * stride * 2 + 1];
2749
+ const s = clamp((x - x0) / (x1 - x0), 0, 1);
2750
+ const t = clamp((yT - y) / (yT - yB), 0, 1);
2751
+ const at = (r, c) => {
2752
+ const k = (r * stride + c) * 2;
2753
+ return [points[k] + offsets[k], points[k + 1] + offsets[k + 1]];
2754
+ };
2755
+ const p00 = at(row, col);
2756
+ const p10 = at(row, col + 1);
2757
+ const p01 = at(row + 1, col);
2758
+ const p11 = at(row + 1, col + 1);
2759
+ const topX = p00[0] + (p10[0] - p00[0]) * s;
2760
+ const topY = p00[1] + (p10[1] - p00[1]) * s;
2761
+ const botX = p01[0] + (p11[0] - p01[0]) * s;
2762
+ const botY = p01[1] + (p11[1] - p01[1]) * s;
2763
+ return [topX + (botX - topX) * t, topY + (botY - topY) * t];
2764
+ }
2765
+
2766
+ // src/auto-rig/body.ts
2767
+ var BODY_WARP_ID = "bodyWarp";
2768
+ var BODY_GRID_PAD = 6;
2769
+ var CHEST_DROP = 0.8;
2770
+ var HIP_BAND = [0.35, 0.75];
2771
+ var LEG_MIN = 0.1;
2772
+ var HIP_FRACTION = 0.5;
2773
+ var BODY_MESH_PX = 48;
2774
+ var BODY_STOPS = [-10, 0, 10];
2775
+ var FOLD_MARGIN = 0.01;
2776
+ function hipLine(layer) {
2777
+ const { bbox, canvasH, rowRuns } = layer;
2778
+ const top = canvasH / 2 - bbox.y;
2779
+ if (rowRuns !== void 0) {
2780
+ const legMin = LEG_MIN * bbox.w;
2781
+ const last = Math.floor(HIP_BAND[1] * bbox.h);
2782
+ for (let k = Math.ceil(HIP_BAND[0] * bbox.h); k <= last; k++) {
2783
+ const runs = rowRuns[k];
2784
+ let legs = 0;
2785
+ for (let i = 0; i < runs.length; i += 2) {
2786
+ if (runs[i + 1] - runs[i] >= legMin) legs++;
2787
+ }
2788
+ if (legs >= 2) return roundTo(top - k, 0.01);
2789
+ }
2790
+ }
2791
+ if (bbox.y + bbox.h === canvasH) return roundTo(top - bbox.h, 0.01);
2792
+ return roundTo(top - HIP_FRACTION * bbox.h, 0.01);
2793
+ }
2794
+ function bodyLattice(body, chin, hh, pivots, anchor) {
2795
+ const b = boxOfLayer(body);
2796
+ const g = (v) => Math.round(roundTo(v, 0.01) * 100);
2797
+ const pad = BODY_GRID_PAD * 100;
2798
+ const all = [chin, ...pivots];
2799
+ const a = g(anchor);
2800
+ const p = Math.min(...all.map((q) => g(q.y)));
2801
+ if (p - a < 2) {
2802
+ throw new LayerGeometryError(
2803
+ `auto-rig: layer "${body.fileName}": its hips (y ${anchor}) leave no row line strictly between them and the lowest of the head's and shoulders' pivots (y ${p / 100}), so the body warp cannot plant the legs below the pivots`
2804
+ );
2805
+ }
2806
+ let full = Math.min(g(chin.y - CHEST_DROP * hh), p - pad);
2807
+ if (full - a < pad) {
2808
+ full = Math.min(Math.max(Math.round((a + p) / 2), a + 1), p - 1);
2809
+ }
2810
+ const top = Math.max(g(b.y1), ...all.map((q) => g(q.y))) + pad;
2811
+ const bottom = Math.min(a, g(b.y0)) - pad;
2812
+ const split = (from, to, cells) => {
2813
+ const n = Math.max(1, Math.min(cells, from - to));
2814
+ const out = [from];
2815
+ for (let i = 1; i < n; i++) {
2816
+ out.push(Math.round(from - (from - to) * i / n));
2817
+ }
2818
+ out.push(to);
2819
+ return out;
2820
+ };
2821
+ const bandA = split(top, full, cellsFor((top - full) / 100, 96, 1, 8));
2822
+ const ramp = split(full, a, cellsFor((full - a) / 100, 96, 2, 8));
2823
+ const rows = [...bandA, ...ramp.slice(1, -1)];
2824
+ const ys = [...rows.map((y) => y / 100), anchor, bottom / 100];
2825
+ const weights = [...rows.map((y) => smoothstep(a, full, y)), 0, 0];
2826
+ const x0 = Math.min(b.x0, ...all.map((q) => q.x)) - BODY_GRID_PAD;
2827
+ const x1 = Math.max(b.x1, ...all.map((q) => q.x)) + BODY_GRID_PAD;
2828
+ const cols = cellsFor(x1 - x0, 96, 2, 8);
2829
+ const xs = [];
2830
+ for (let c = 0; c <= cols; c++) {
2831
+ xs.push(roundTo(x0 + (x1 - x0) * c / cols, 0.01));
2832
+ }
2833
+ return { xs, ys, weights, yFull: full / 100 };
2834
+ }
2835
+ function bodyMesh(b, rows) {
2836
+ const lines = [b.y1, ...rows.filter((y) => y > b.y0 && y < b.y1), b.y0];
2837
+ const ys = [lines[0]];
2838
+ for (let i = 1; i < lines.length; i++) {
2839
+ const [from, to] = [lines[i - 1], lines[i]];
2840
+ const n = Math.ceil((from - to) / BODY_MESH_PX);
2841
+ for (let k = 1; k < n; k++) ys.push(from - (from - to) * k / n);
2842
+ ys.push(to);
2843
+ }
2844
+ const cols = cellsFor(bw(b), BODY_MESH_PX, 2, 16);
2845
+ const vertices = [];
2846
+ const uvs = [];
2847
+ for (const y of ys) {
2848
+ const vy = roundTo((y - cy(b)) / bh(b), 1e-5);
2849
+ for (let c = 0; c <= cols; c++) {
2850
+ const vx = roundTo(c / cols - 0.5, 1e-5);
2851
+ vertices.push(vx, vy);
2852
+ uvs.push(roundTo(vx + 0.5, 1e-5), roundTo(0.5 - vy, 1e-5));
2853
+ }
2854
+ }
2855
+ const indices = [];
2856
+ for (let r = 0; r + 1 < ys.length; r++) {
2857
+ for (let c = 0; c < cols; c++) {
2858
+ const tl = r * (cols + 1) + c;
2859
+ const tr = tl + 1;
2860
+ const bl = tl + cols + 1;
2861
+ const br = bl + 1;
2862
+ indices.push(bl, br, tl, tl, br, tr);
2863
+ }
2864
+ }
2865
+ return { vertices, uvs, indices };
2866
+ }
2867
+ function buildBodyWarp({
2868
+ body,
2869
+ chin,
2870
+ hh,
2871
+ pivots
2872
+ }) {
2873
+ const b = boxOfLayer(body);
2874
+ const anchor = hipLine(body);
2875
+ const { xs, ys, weights } = bodyLattice(body, chin, hh, pivots, anchor);
2876
+ const hx = cx(b);
2877
+ const turn = (x, t) => t * BODY.slide * hh - Math.abs(t) * BODY.narrow * (x - hx);
2878
+ const roll = (x, y, deg) => {
2879
+ const c = Math.cos(deg * DEG) - 1;
2880
+ const s = Math.sin(deg * DEG);
2881
+ const [dx, dy] = [x - hx, y - anchor];
2882
+ return [c * dx - s * dy, s * dx + c * dy];
2883
+ };
2884
+ const warp = (parameter, values, field) => {
2885
+ const extreme = Math.max(...values.map(Math.abs));
2886
+ return {
2887
+ parameter,
2888
+ keyforms: values.map((value) => ({
2889
+ value,
2890
+ offsets: ys.flatMap(
2891
+ (y, r) => xs.flatMap((x) => {
2892
+ const [dx, dy] = field(x, y, value / extreme);
2893
+ return [
2894
+ roundTo(weights[r] * dx, 0.01),
2895
+ roundTo(weights[r] * dy, 0.01)
2896
+ ];
2897
+ })
2898
+ )
2899
+ }))
2900
+ };
2901
+ };
2902
+ const deformer = {
2903
+ kind: "warp",
2904
+ id: BODY_WARP_ID,
2905
+ grid: {
2906
+ cols: xs.length - 1,
2907
+ rows: ys.length - 1,
2908
+ points: ys.flatMap((y) => xs.flatMap((x) => [x, y]))
2909
+ },
2910
+ warps: [
2911
+ warp(import_format5.StandardParameter.BodyAngleX, BODY_STOPS, (x, _y, t) => [turn(x, t), 0]),
2912
+ warp(import_format5.StandardParameter.BodyAngleY, BODY_STOPS, (_x, _y, t) => [0, t * BODY.bow * hh]),
2913
+ warp(
2914
+ import_format5.StandardParameter.BodyAngleZ,
2915
+ BODY_STOPS,
2916
+ (x, y, t) => roll(x, y, -t * BODY.rollDeg)
2917
+ ),
2918
+ warp(import_format5.StandardParameter.AngleX, X_STOPS, (x, _y, t) => [BODY.followX * turn(x, t), 0]),
2919
+ warp(import_format5.StandardParameter.AngleZ, Z_STOPS, (x, y, t) => roll(x, y, -t * BODY.followRoll)),
2920
+ warp(import_format5.StandardParameter.Breath, [0, 1], (_x, _y, t) => [0, t * AMPLITUDE.breathBody * hh])
2921
+ ]
2922
+ };
2923
+ refuseFold(body, deformer, anchor, [chin, ...pivots]);
2924
+ return { deformer, mesh: bodyMesh(b, ys) };
2925
+ }
2926
+ function refuseFold(body, d, anchor, pivots) {
2927
+ const { cols, rows, points } = d.grid;
2928
+ const warps = d.warps ?? [];
2929
+ const stride = cols + 1;
2930
+ for (let r = 0; r < rows; r++) {
2931
+ for (let c = 0; c < cols; c++) {
2932
+ const tl = r * stride + c;
2933
+ const [tr, bl, br] = [tl + 1, tl + stride, tl + stride + 1];
2934
+ const corners = [
2935
+ [bl, br, tl],
2936
+ [br, tr, bl],
2937
+ [tr, tl, br],
2938
+ [tl, bl, tr]
2939
+ ];
2940
+ for (const [p, q, s] of corners) {
2941
+ const fold = leastArea(points, warps, p, q, s);
2942
+ if (fold.least >= FOLD_MARGIN * fold.rest) continue;
2943
+ const lowest = roundTo(Math.min(...pivots.map((v) => v.y)), 0.01);
2944
+ const pose = warps.map((w, i) => `${w.parameter} ${w.keyforms[fold.stops[i]].value}`).join(", ");
2945
+ throw new LayerGeometryError(
2946
+ `auto-rig: layer "${body.fileName}": the body is too short between its hips (y ${anchor}) and the lowest of the head's and shoulders' pivots (y ${lowest}) for its motion: its body warp's cell from x ${points[tl * 2]} to ${points[tr * 2]}, y ${points[tl * 2 + 1]} to ${points[bl * 2 + 1]}, would fold at or near ${pose}`
2947
+ );
2948
+ }
2949
+ }
2950
+ }
2951
+ }
2952
+ function leastArea(points, warps, p, q, s) {
2953
+ const moves = warps.map(
2954
+ (w) => w.keyforms.map(({ offsets: o }) => [
2955
+ o[q * 2] - o[p * 2],
2956
+ o[q * 2 + 1] - o[p * 2 + 1],
2957
+ o[s * 2] - o[p * 2],
2958
+ o[s * 2 + 1] - o[p * 2 + 1]
2959
+ ])
2960
+ );
2961
+ const cross = (e) => e[0] * e[3] - e[1] * e[2];
2962
+ const edges = [
2963
+ points[q * 2] - points[p * 2],
2964
+ points[q * 2 + 1] - points[p * 2 + 1],
2965
+ points[s * 2] - points[p * 2],
2966
+ points[s * 2 + 1] - points[p * 2 + 1]
2967
+ ];
2968
+ let least = Infinity;
2969
+ let stops = [];
2970
+ const at = [];
2971
+ const walk = (w, ax, ay, bx, by) => {
2972
+ if (w === moves.length) {
2973
+ const area = ax * by - ay * bx;
2974
+ if (area < least) [least, stops] = [area, [...at]];
2975
+ return;
2976
+ }
2977
+ moves[w].forEach(([mx, my, nx, ny], k) => {
2978
+ at[w] = k;
2979
+ walk(w + 1, ax + mx, ay + my, bx + nx, by + ny);
2980
+ });
2981
+ };
2982
+ walk(0, edges[0], edges[1], edges[2], edges[3]);
2983
+ for (const m of moves) {
2984
+ let most = 0;
2985
+ for (let k = 1; k < m.length; k++) {
2986
+ most = Math.max(most, cross(m[k].map((v, i) => v - m[k - 1][i])));
2987
+ }
2988
+ least -= most / 4;
2989
+ }
2990
+ return { rest: cross(edges), least, stops };
2991
+ }
2992
+ function headOwnRoll(hasBody) {
2993
+ return hasBody ? ROLL_DEG - BODY.followRoll : ROLL_DEG;
2994
+ }
2995
+ function headOwnBreath(hasBody) {
2996
+ return hasBody ? AMPLITUDE.breathHead - AMPLITUDE.breathBody : AMPLITUDE.breathHead;
2997
+ }
2998
+
2999
+ // src/auto-rig/arms.ts
3000
+ var ELBOW_AT = 0.42;
3001
+ var CAP_ROWS = [0.1, 0.3];
3002
+ var CAP_SEGMENTS = 12;
3003
+ var ARM_RANGE = [-30, 150];
3004
+ var ARM_PARAMS = {
3005
+ arm_L: { arm: import_format6.StandardParameter.ArmLeft, elbow: import_format6.StandardParameter.ElbowLeft },
3006
+ arm_R: { arm: import_format6.StandardParameter.ArmRight, elbow: import_format6.StandardParameter.ElbowRight }
3007
+ };
3008
+ var ARM_IDS = {
3009
+ arm_L: {
3010
+ forearm: "forearm_L",
3011
+ armDeformer: "armDeformer_L",
3012
+ forearmDeformer: "forearmDeformer_L"
3013
+ },
3014
+ arm_R: {
3015
+ forearm: "forearm_R",
3016
+ armDeformer: "armDeformer_R",
3017
+ forearmDeformer: "forearmDeformer_R"
3018
+ }
3019
+ };
3020
+ function armGeometry(layer, bodyAxisX) {
3021
+ const { bbox, canvasW, canvasH, rowRuns } = layer;
3022
+ const b = boxOfLayer(layer);
3023
+ const span = (k) => {
3024
+ const runs = rowRuns?.[k] ?? [];
3025
+ if (runs.length === 0) return [bbox.x, bbox.x + bbox.w];
3026
+ let [s2, e2] = [runs[0], runs[1]];
3027
+ for (let i = 2; i < runs.length; i += 2) {
3028
+ if (runs[i + 1] - runs[i] > e2 - s2) [s2, e2] = [runs[i], runs[i + 1]];
3029
+ }
3030
+ return [s2, e2];
3031
+ };
3032
+ const centreX = (k) => {
3033
+ const [s2, e2] = span(k);
3034
+ return (s2 + e2) / 2 - canvasW / 2;
3035
+ };
3036
+ const rowY = (k) => canvasH / 2 - (bbox.y + k + 0.5);
3037
+ const painted = rowRuns?.flatMap((runs, k) => runs.length > 0 ? [k] : []) ?? [];
3038
+ const first = painted[0] ?? 0;
3039
+ const last = painted.at(-1) ?? bbox.h - 1;
3040
+ const lo = Math.ceil(CAP_ROWS[0] * bbox.h);
3041
+ const hi = Math.max(lo, Math.floor(CAP_ROWS[1] * bbox.h));
3042
+ const widths = [];
3043
+ for (let k = lo; k <= hi; k++) {
3044
+ const [s2, e2] = span(k);
3045
+ widths.push(e2 - s2);
3046
+ }
3047
+ widths.sort((p, q) => p - q);
3048
+ const ru = widths[widths.length >> 1] / 2;
3049
+ const ks = first + ru;
3050
+ const shoulder = {
3051
+ x: roundTo(centreX(Math.round(ks)), 0.01),
3052
+ y: roundTo(rowY(ks), 0.01)
3053
+ };
3054
+ const ke = Math.round(ks + ELBOW_AT * (last - ks));
3055
+ if (ke <= ks) {
3056
+ throw new LayerGeometryError(
3057
+ `auto-rig: layer "${layer.fileName}": the arm is too short for its width to place a shoulder above an elbow (draw the arm hanging, shoulder at the top)`
3058
+ );
3059
+ }
3060
+ const elbow = { x: roundTo(centreX(ke), 0.01), y: roundTo(rowY(ke), 0.01) };
3061
+ const [s, e] = span(ke);
3062
+ const capRadius = Math.min(
3063
+ (e - s) / 2 + 1,
3064
+ elbow.x - b.x0,
3065
+ b.x1 - elbow.x,
3066
+ b.y1 - elbow.y
3067
+ );
3068
+ return {
3069
+ shoulder,
3070
+ shoulderRadius: ru,
3071
+ elbow,
3072
+ capRadius,
3073
+ side: shoulder.x > bodyAxisX ? 1 : -1
3074
+ };
3075
+ }
3076
+ function meshOf(vertices, indices) {
3077
+ const uvs = vertices.map(
3078
+ (v, i) => roundTo(i % 2 === 0 ? v + 0.5 : 0.5 - v, 1e-5)
3079
+ );
3080
+ return { vertices, uvs, indices };
3081
+ }
3082
+ function armParts(role, layer, g, order) {
3083
+ const b = boxOfLayer(layer);
3084
+ const lx = (x) => roundTo((x - cx(b)) / bw(b), 1e-5);
3085
+ const ly = (y) => roundTo((y - cy(b)) / bh(b), 1e-5);
3086
+ const seam = ly(g.elbow.y);
3087
+ const band = (top, bottom) => [
3088
+ -0.5,
3089
+ top,
3090
+ 0.5,
3091
+ top,
3092
+ -0.5,
3093
+ bottom,
3094
+ 0.5,
3095
+ bottom
3096
+ ];
3097
+ const quad = [2, 3, 0, 0, 3, 1];
3098
+ const cap = [lx(g.elbow.x), seam];
3099
+ const fan = [];
3100
+ for (let i = 0; i <= CAP_SEGMENTS; i++) {
3101
+ const a = Math.PI * i / CAP_SEGMENTS;
3102
+ cap.push(
3103
+ lx(g.elbow.x + g.capRadius * Math.cos(a)),
3104
+ ly(g.elbow.y + g.capRadius * Math.sin(a))
3105
+ );
3106
+ if (i > 0) fan.push(4, 4 + i, 5 + i);
3107
+ }
3108
+ const part = (id, deformer, at, mesh) => ({
3109
+ id,
3110
+ color: [1, 1, 1, 1],
3111
+ width: bw(b),
3112
+ height: bh(b),
3113
+ transform: { x: cx(b), y: cy(b) },
3114
+ order: at,
3115
+ deformer,
3116
+ mesh
3117
+ });
3118
+ const ids = ARM_IDS[role];
3119
+ return [
3120
+ part(role, ids.armDeformer, order, meshOf(band(0.5, seam), quad)),
3121
+ part(
3122
+ ids.forearm,
3123
+ ids.forearmDeformer,
3124
+ order + 1,
3125
+ meshOf([...band(seam, -0.5), ...cap], [...quad, ...fan])
3126
+ )
3127
+ ];
3128
+ }
3129
+ function armDeformers(role, g) {
3130
+ const ids = ARM_IDS[role];
3131
+ const params = ARM_PARAMS[role];
3132
+ const rotate = (parameter) => ({
3133
+ parameter,
3134
+ channel: "rotate",
3135
+ from: g.side * ARM_RANGE[0],
3136
+ to: g.side * ARM_RANGE[1]
3137
+ });
3138
+ return [
3139
+ {
3140
+ id: ids.armDeformer,
3141
+ parent: BODY_WARP_ID,
3142
+ pivot: { ...g.shoulder },
3143
+ bindings: [rotate(params.arm)]
3144
+ },
3145
+ {
3146
+ id: ids.forearmDeformer,
3147
+ parent: ids.armDeformer,
3148
+ pivot: { ...g.elbow },
3149
+ bindings: [rotate(params.elbow)]
3150
+ }
3151
+ ];
3152
+ }
3153
+
2436
3154
  // src/auto-rig/checks.ts
2437
3155
  var finite = (v) => typeof v === "number" && Number.isFinite(v);
2438
3156
  var isObject = (v) => typeof v === "object" && v !== null;
@@ -2536,6 +3254,7 @@ function checkLayers(layers, canvas) {
2536
3254
  if (missing.length > 0) {
2537
3255
  throw new Error(`auto-rig: missing required role(s) ${missing.join(", ")}`);
2538
3256
  }
3257
+ checkArmsHaveBody(seen);
2539
3258
  }
2540
3259
  function checkHeadEdges(edges, byRole) {
2541
3260
  if (edges === void 0) return;
@@ -2616,131 +3335,45 @@ function checkStrandEdges(strand, irises, hair) {
2616
3335
  for (const [k, x] of [
2617
3336
  ["irisOuter", outer],
2618
3337
  ["irisInner", inner]
2619
- ]) {
2620
- if (x < iris.x0 || x > iris.x1) {
2621
- throw new TurnTargetError(`${at}.${k} ${x} is off its iris's crop`);
2622
- }
2623
- }
2624
- if (sign * (outer - inner) <= 0) {
2625
- throw new TurnTargetError(
2626
- `${at}.irisOuter ${outer} is not strictly outward of irisInner ${inner}`
2627
- );
2628
- }
2629
- for (const [k, x] of [
2630
- ["runOuter", runOuter],
2631
- ["runFace", runFace]
2632
- ]) {
2633
- if (x !== null && (x < hair.x0 || x > hair.x1)) {
2634
- throw new TurnTargetError(`${at}.${k} ${x} is off hair_front's crop`);
2635
- }
2636
- }
2637
- if (sign * (runOuter - cx(iris)) <= 0) {
2638
- throw new TurnTargetError(
2639
- `${at}.runOuter ${runOuter} is not strictly outward of its iris's centre`
2640
- );
2641
- }
2642
- if (runFace !== null && sign * (runOuter - runFace) <= 0) {
2643
- throw new TurnTargetError(
2644
- `${at}.runOuter ${runOuter} is not strictly outward of runFace ${runFace}`
2645
- );
2646
- }
2647
- if (runFace !== null && sign * (runFace - cx(other)) <= 0) {
2648
- throw new TurnTargetError(
2649
- `${at}.runFace ${runFace} reaches the other iris's centre; a run that does is a fringe, runFace null`
2650
- );
2651
- }
2652
- }
2653
- }
2654
-
2655
- // src/auto-rig/context.ts
2656
- var import_format4 = require("@ikijs/format");
2657
-
2658
- // src/auto-rig/grid.ts
2659
- var X_STOPS = [-30, 0, 30];
2660
- var Y_STOPS = [-30, 0, 30];
2661
- function lattice(box, cell, min, max) {
2662
- const cols = cellsFor(box.x1 - box.x0, cell, min, max);
2663
- const rows = cellsFor(box.y1 - box.y0, cell, min, max);
2664
- const points = [];
2665
- for (let r = 0; r <= rows; r++) {
2666
- const y = box.y1 - (box.y1 - box.y0) * r / rows;
2667
- for (let c = 0; c <= cols; c++) {
2668
- points.push(
2669
- roundTo(box.x0 + (box.x1 - box.x0) * c / cols, 0.01),
2670
- roundTo(y, 0.01)
2671
- );
2672
- }
2673
- }
2674
- return { box, cols, rows, points };
2675
- }
2676
- function bake(l, field) {
2677
- const out = [];
2678
- for (const ay of Y_STOPS) {
2679
- for (const ax of X_STOPS) {
2680
- const offsets = [];
2681
- for (let k = 0; k < l.points.length; k += 2) {
2682
- if (ax === 0 && ay === 0) {
2683
- offsets.push(0, 0);
2684
- continue;
2685
- }
2686
- const [dx, dy] = field(l.points[k], l.points[k + 1], ax, ay);
2687
- offsets.push(roundTo(dx, 0.1), roundTo(dy, 0.1));
3338
+ ]) {
3339
+ if (x < iris.x0 || x > iris.x1) {
3340
+ throw new TurnTargetError(`${at}.${k} ${x} is off its iris's crop`);
2688
3341
  }
2689
- out.push(offsets);
2690
3342
  }
2691
- }
2692
- return out;
2693
- }
2694
- function keyformAt(keyforms, ax) {
2695
- const j = Y_STOPS.indexOf(0);
2696
- const row = X_STOPS.map((_, i) => keyforms[j * X_STOPS.length + i]);
2697
- return blendStops(X_STOPS, row, ax);
2698
- }
2699
- function blendStops(values, offsets, v) {
2700
- const x = clamp(v, values[0], values[values.length - 1]);
2701
- let i = 0;
2702
- while (i < values.length - 2 && x > values[i + 1]) i++;
2703
- const t = (x - values[i]) / (values[i + 1] - values[i]);
2704
- return offsets[i].map((o, k) => o + (offsets[i + 1][k] - o) * t);
2705
- }
2706
- function land(l, offsets, x, y) {
2707
- const { cols, rows, points } = l;
2708
- const stride = cols + 1;
2709
- let col = cols - 1;
2710
- for (let c = 0; c < cols; c++) {
2711
- if (x < points[(c + 1) * 2]) {
2712
- col = c;
2713
- break;
3343
+ if (sign * (outer - inner) <= 0) {
3344
+ throw new TurnTargetError(
3345
+ `${at}.irisOuter ${outer} is not strictly outward of irisInner ${inner}`
3346
+ );
2714
3347
  }
2715
- }
2716
- let row = rows - 1;
2717
- for (let r = 0; r < rows; r++) {
2718
- if (y > points[(r + 1) * stride * 2 + 1]) {
2719
- row = r;
2720
- break;
3348
+ for (const [k, x] of [
3349
+ ["runOuter", runOuter],
3350
+ ["runFace", runFace]
3351
+ ]) {
3352
+ if (x !== null && (x < hair.x0 || x > hair.x1)) {
3353
+ throw new TurnTargetError(`${at}.${k} ${x} is off hair_front's crop`);
3354
+ }
3355
+ }
3356
+ if (sign * (runOuter - cx(iris)) <= 0) {
3357
+ throw new TurnTargetError(
3358
+ `${at}.runOuter ${runOuter} is not strictly outward of its iris's centre`
3359
+ );
3360
+ }
3361
+ if (runFace !== null && sign * (runOuter - runFace) <= 0) {
3362
+ throw new TurnTargetError(
3363
+ `${at}.runOuter ${runOuter} is not strictly outward of runFace ${runFace}`
3364
+ );
3365
+ }
3366
+ if (runFace !== null && sign * (runFace - cx(other)) <= 0) {
3367
+ throw new TurnTargetError(
3368
+ `${at}.runFace ${runFace} reaches the other iris's centre; a run that does is a fringe, runFace null`
3369
+ );
2721
3370
  }
2722
3371
  }
2723
- const x0 = points[col * 2];
2724
- const x1 = points[(col + 1) * 2];
2725
- const yT = points[row * stride * 2 + 1];
2726
- const yB = points[(row + 1) * stride * 2 + 1];
2727
- const s = clamp((x - x0) / (x1 - x0), 0, 1);
2728
- const t = clamp((yT - y) / (yT - yB), 0, 1);
2729
- const at = (r, c) => {
2730
- const k = (r * stride + c) * 2;
2731
- return [points[k] + offsets[k], points[k + 1] + offsets[k + 1]];
2732
- };
2733
- const p00 = at(row, col);
2734
- const p10 = at(row, col + 1);
2735
- const p01 = at(row + 1, col);
2736
- const p11 = at(row + 1, col + 1);
2737
- const topX = p00[0] + (p10[0] - p00[0]) * s;
2738
- const topY = p00[1] + (p10[1] - p00[1]) * s;
2739
- const botX = p01[0] + (p11[0] - p01[0]) * s;
2740
- const botY = p01[1] + (p11[1] - p01[1]) * s;
2741
- return [topX + (botX - topX) * t, topY + (botY - topY) * t];
2742
3372
  }
2743
3373
 
3374
+ // src/auto-rig/context.ts
3375
+ var import_format7 = require("@ikijs/format");
3376
+
2744
3377
  // src/auto-rig/head.ts
2745
3378
  function outerEdge(runs, axisX, side) {
2746
3379
  if (runs.length === 0) return void 0;
@@ -3770,6 +4403,7 @@ function familyField(m, family) {
3770
4403
  case "hair_back":
3771
4404
  return hairField(m, m.hairBack, NOD.hairBack);
3772
4405
  case "body":
4406
+ case "arm":
3773
4407
  return () => [0, 0];
3774
4408
  }
3775
4409
  }
@@ -3869,7 +4503,7 @@ function renderedLander(turn, parts, grids, from = /* @__PURE__ */ new Map()) {
3869
4503
  const family = roleSpec(role).family;
3870
4504
  const part = parts.find((p) => p.id === role);
3871
4505
  const g = grids.get(family);
3872
- const warp = part?.warps?.find((w) => w.parameter === import_format4.StandardParameter.AngleX);
4506
+ const warp = part?.warps?.find((w) => w.parameter === import_format7.StandardParameter.AngleX);
3873
4507
  if (part === void 0 || part.mesh === void 0 || !g && !warp) {
3874
4508
  return x + field(role)(x, y, ax, 0)[0];
3875
4509
  }
@@ -4578,177 +5212,6 @@ function buildTurn(frame, landmarks, q) {
4578
5212
  };
4579
5213
  }
4580
5214
 
4581
- // src/auto-rig/drivers.ts
4582
- var import_format5 = require("@ikijs/format");
4583
- var SWAY_RANGE = 20;
4584
- var SWAY_CURVE = 1.4;
4585
- var HAIR_HANG = 0.35;
4586
- var Z_STOPS = [-30, 0, 30];
4587
- function localWarp(parameter, mesh, box, values, f) {
4588
- const pts = meshPoints(mesh, box);
4589
- const w = bw(box);
4590
- const h = bh(box);
4591
- return {
4592
- parameter,
4593
- keyforms: values.map((value) => ({
4594
- value,
4595
- offsets: pts.flatMap((p, i) => {
4596
- const [dx, dy] = f(p, value, i);
4597
- return [roundTo(dx / w, 1e-4), roundTo(dy / h, 1e-4)];
4598
- })
4599
- }))
4600
- };
4601
- }
4602
- var rollOf = (v) => -v / 30 * ROLL_DEG * DEG;
4603
- function motionReach(warps, mesh, box, extra = [0, 0]) {
4604
- const out = {
4605
- left: extra[0],
4606
- right: extra[0],
4607
- bottom: extra[1],
4608
- top: extra[1]
4609
- };
4610
- if (mesh === void 0) return out;
4611
- const pts = meshPoints(mesh, box);
4612
- for (const warp of warps ?? []) {
4613
- let l = 0;
4614
- let r = 0;
4615
- let b = 0;
4616
- let t = 0;
4617
- for (const k of warp.keyforms) {
4618
- pts.forEach(([x, y], i) => {
4619
- const px = x + k.offsets[2 * i] * bw(box);
4620
- const py = y + k.offsets[2 * i + 1] * bh(box);
4621
- l = Math.max(l, box.x0 - px);
4622
- r = Math.max(r, px - box.x1);
4623
- b = Math.max(b, box.y0 - py);
4624
- t = Math.max(t, py - box.y1);
4625
- });
4626
- }
4627
- out.left += l;
4628
- out.right += r;
4629
- out.bottom += b;
4630
- out.top += t;
4631
- }
4632
- return out;
4633
- }
4634
- function blinkFold(param, mesh, box, travel, eye = box) {
4635
- const yc = eye.y1 - travel * bh(eye);
4636
- return localWarp(
4637
- param,
4638
- mesh,
4639
- box,
4640
- [0, 1],
4641
- ([, y], v) => v === 1 ? [0, 0] : [0, yc - y]
4642
- );
4643
- }
4644
- function lashFold(param, mesh, lash, eye, travel) {
4645
- const yc = eye.y1 - travel * bh(eye) - 0.04 * bh(eye);
4646
- const c = cx(lash);
4647
- const half = bw(lash) / 2;
4648
- const sag = 0.3 * bh(lash);
4649
- return localWarp(param, mesh, lash, [0, 1], ([x, y], v) => {
4650
- if (v === 1) return [0, 0];
4651
- const u = (x - c) / half;
4652
- return [0, yc + (y - lash.y0) * 0.5 - sag * (1 - u * u) - y];
4653
- });
4654
- }
4655
- function gazeBindings(irisW, share = 1) {
4656
- const gx = AMPLITUDE.gazeX * irisW * share;
4657
- const gy = AMPLITUDE.gazeY * irisW * share;
4658
- return [
4659
- {
4660
- parameter: import_format5.StandardParameter.EyeballX,
4661
- channel: "translateX",
4662
- from: -round1(gx),
4663
- to: round1(gx)
4664
- },
4665
- {
4666
- parameter: import_format5.StandardParameter.EyeballY,
4667
- channel: "translateY",
4668
- from: -round1(gy),
4669
- to: round1(gy)
4670
- }
4671
- ];
4672
- }
4673
- function gazeReach(irisW, share = 1) {
4674
- return [AMPLITUDE.gazeX * irisW * share, AMPLITUDE.gazeY * irisW * share];
4675
- }
4676
- function browReach(hh) {
4677
- return AMPLITUDE.brow * hh;
4678
- }
4679
- function browBindings(side, reach) {
4680
- const y = side === "L" ? import_format5.StandardParameter.BrowLeftY : import_format5.StandardParameter.BrowRightY;
4681
- const a = side === "L" ? import_format5.StandardParameter.BrowLeftAngle : import_format5.StandardParameter.BrowRightAngle;
4682
- return [
4683
- {
4684
- parameter: y,
4685
- channel: "translateY",
4686
- from: -round1(reach),
4687
- to: round1(reach)
4688
- },
4689
- { parameter: a, channel: "rotate", from: -BROW_TILT, to: BROW_TILT }
4690
- ];
4691
- }
4692
- var BROW_TILT = 15;
4693
- function mouthForm(mesh, box) {
4694
- const a = 0.3 * bh(box);
4695
- const c = cx(box);
4696
- const half = bw(box) / 2;
4697
- return localWarp(import_format5.StandardParameter.MouthForm, mesh, box, [-1, 0, 1], ([x], v) => {
4698
- if (v === 0) return [0, 0];
4699
- const u = (x - c) / half;
4700
- const widen = v > 0 ? 0.05 : 0.03;
4701
- return [v * widen * (x - c), v * a * (u * u - 0.3)];
4702
- });
4703
- }
4704
- function mouthOpenGrow(mesh, box) {
4705
- const c = cx(box);
4706
- return localWarp(
4707
- import_format5.StandardParameter.MouthOpen,
4708
- mesh,
4709
- box,
4710
- [0, 1],
4711
- ([x, y], v) => v === 1 ? [(AMPLITUDE.mouthOpenWidth - 1) * (x - c), 0] : [0, box.y1 + (y - box.y1) * 0.2 - y]
4712
- );
4713
- }
4714
- function mouthWiden(mesh, box) {
4715
- const c = cx(box);
4716
- return localWarp(import_format5.StandardParameter.MouthOpen, mesh, box, [0, 1], ([x], v) => [
4717
- v * (AMPLITUDE.mouthOpenWidth - 1) * (x - c),
4718
- 0
4719
- ]);
4720
- }
4721
- function hairSway(param, mesh, box, amp) {
4722
- return localWarp(
4723
- param,
4724
- mesh,
4725
- box,
4726
- [-SWAY_RANGE, 0, SWAY_RANGE],
4727
- ([, y], v) => {
4728
- const t = Math.max(0, (box.y1 - y) / bh(box));
4729
- return [v / SWAY_RANGE * amp * t ** SWAY_CURVE, 0];
4730
- }
4731
- );
4732
- }
4733
- function tiltHang(mesh, box, pivot, weight) {
4734
- return localWarp(import_format5.StandardParameter.AngleZ, mesh, box, Z_STOPS, ([x, y], v, i) => {
4735
- const g = weight(y, i);
4736
- if (g === 0 || v === 0) return [0, 0];
4737
- const a = -g * rollOf(v);
4738
- const px = x - pivot[0];
4739
- const py = y - pivot[1];
4740
- return [
4741
- pivot[0] + px * Math.cos(a) - py * Math.sin(a) - x,
4742
- pivot[1] + px * Math.sin(a) + py * Math.cos(a) - y
4743
- ];
4744
- });
4745
- }
4746
- function hairHangWeight(pivotY, bottom) {
4747
- if (bottom >= pivotY) return () => 0;
4748
- return (y) => HAIR_HANG * smoothstep(pivotY, bottom, y);
4749
- }
4750
- var round1 = (v) => roundTo(v, 0.1);
4751
-
4752
5215
  // src/auto-rig/generate.ts
4753
5216
  var MESH_CELLS = {
4754
5217
  face: { px: 28, min: 4, max: 24 },
@@ -4775,6 +5238,7 @@ var WARP_ID = {
4775
5238
  mouth: "mouthWarp"
4776
5239
  };
4777
5240
  var isGridFamily = (f) => f in WARP_ID;
5241
+ var isArm = (spec) => spec.family === "arm";
4778
5242
  var GRID_PAD = 6;
4779
5243
  function generateIkiFromLayerSet(layers, canvas, options = {}) {
4780
5244
  checkLayers(layers, canvas);
@@ -4823,6 +5287,18 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4823
5287
  chinSlide: chinSlide(fit.model)
4824
5288
  });
4825
5289
  const turn = { ...fit.model, frame };
5290
+ const arms = /* @__PURE__ */ new Map();
5291
+ for (const spec of ROLE_TABLE) {
5292
+ const layer = byRole.get(spec.role);
5293
+ if (layer === void 0 || !isArm(spec)) continue;
5294
+ arms.set(spec.role, armGeometry(layer, cx(box("body"))));
5295
+ }
5296
+ const bodyWarp = has("body") ? buildBodyWarp({
5297
+ body: byRole.get("body"),
5298
+ chin: { x: frame.axisX, y: frame.chinY },
5299
+ hh: frame.hh,
5300
+ pivots: [...arms.values()].map((g) => g.shoulder)
5301
+ }) : void 0;
4826
5302
  const parts = [];
4827
5303
  const reach = /* @__PURE__ */ new Map();
4828
5304
  let regionOf = [];
@@ -4830,6 +5306,12 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4830
5306
  for (const spec of ROLE_TABLE) {
4831
5307
  const layer = byRole.get(spec.role);
4832
5308
  if (layer === void 0) continue;
5309
+ if (isArm(spec)) {
5310
+ parts.push(
5311
+ ...armParts(spec.role, layer, arms.get(spec.role), parts.length)
5312
+ );
5313
+ continue;
5314
+ }
4833
5315
  const built = buildPart(
4834
5316
  spec,
4835
5317
  boxOfLayer(layer),
@@ -4848,7 +5330,9 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4848
5330
  }
4849
5331
  const grids = /* @__PURE__ */ new Map();
4850
5332
  for (const family of Object.keys(WARP_ID)) {
4851
- const members = parts.filter((p) => roleSpec(p.id).family === family);
5333
+ const members = parts.filter(
5334
+ (p) => roleSpec(roleOfPart(p.id)).family === family
5335
+ );
4852
5336
  if (members.length === 0) continue;
4853
5337
  const area = unionBoxes(
4854
5338
  members.map((p) => {
@@ -4870,13 +5354,19 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4870
5354
  });
4871
5355
  }
4872
5356
  for (const part of parts) {
4873
- const family = roleSpec(part.id).family;
5357
+ const family = roleSpec(roleOfPart(part.id)).family;
4874
5358
  if (isGridFamily(family)) {
4875
5359
  part.deformer = WARP_ID[family];
4876
5360
  continue;
4877
5361
  }
4878
- part.deformer = family === "body" ? "bodyDeformer" : "headDeformer";
4879
- if (family === "body" || part.mesh === void 0) continue;
5362
+ if (family === "body") {
5363
+ part.deformer = BODY_WARP_ID;
5364
+ part.mesh = bodyWarp.mesh;
5365
+ continue;
5366
+ }
5367
+ if (family === "arm") continue;
5368
+ part.deformer = "headDeformer";
5369
+ if (part.mesh === void 0) continue;
4880
5370
  const own = familyField(turn, family);
4881
5371
  const fieldOf = part.id === "face" ? plateFields(turn, regionOf, own) : () => own;
4882
5372
  part.warps = [
@@ -4886,17 +5376,17 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4886
5376
  }
4887
5377
  const parameters = declareParameters(new Set(byRole.keys()));
4888
5378
  const model = {
4889
- version: import_format6.IKI_FORMAT_VERSION,
5379
+ version: import_format8.IKI_FORMAT_VERSION,
4890
5380
  name: "auto-rigged",
4891
5381
  canvas: { width: canvas.width, height: canvas.height },
4892
5382
  parameters,
4893
5383
  parts,
4894
- deformers: deformers(frame, grids, has("body") ? box("body") : void 0),
5384
+ deformers: deformers(frame, grids, bodyWarp?.deformer, arms),
4895
5385
  ...has("hair_front") ? { physics: hairPhysics() } : {},
4896
5386
  expressions: defaultExpressions(new Set(parameters.map((p) => p.id))),
4897
5387
  motions: DEFAULT_MOTIONS
4898
5388
  };
4899
- const valid = (0, import_format6.parseIkiModel)(model);
5389
+ const valid = (0, import_format8.parseIkiModel)(model);
4900
5390
  if (hasNose && options.onTurnSolved) {
4901
5391
  options.onTurnSolved(
4902
5392
  turnReport(
@@ -4911,14 +5401,14 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4911
5401
  function turnWarps(mesh, b, fieldOf) {
4912
5402
  return [
4913
5403
  localWarp(
4914
- import_format6.StandardParameter.AngleX,
5404
+ import_format8.StandardParameter.AngleX,
4915
5405
  mesh,
4916
5406
  b,
4917
5407
  X_STOPS,
4918
5408
  ([x, y], v, i) => v === 0 ? [0, 0] : [fieldOf(i)(x, y, v, 0)[0], 0]
4919
5409
  ),
4920
5410
  localWarp(
4921
- import_format6.StandardParameter.AngleY,
5411
+ import_format8.StandardParameter.AngleY,
4922
5412
  mesh,
4923
5413
  b,
4924
5414
  Y_STOPS,
@@ -4966,16 +5456,22 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
4966
5456
  }
4967
5457
  if (mesh !== void 0) part.mesh = mesh;
4968
5458
  const side = spec.role.endsWith("_L") ? "L" : "R";
4969
- const eyeOpen = side === "L" ? import_format6.StandardParameter.EyeOpenLeft : import_format6.StandardParameter.EyeOpenRight;
5459
+ const eyeOpen = side === "L" ? import_format8.StandardParameter.EyeOpenLeft : import_format8.StandardParameter.EyeOpenRight;
4970
5460
  const hh = frame.hh;
4971
5461
  switch (spec.role) {
4972
5462
  case "face":
4973
5463
  if (region?.some((r) => r === "neck")) {
4974
5464
  const isNeck = region.map((r) => r === "neck");
4975
5465
  warps.push(
4976
- tiltHang(mesh, b, pivot, (_y, i) => isNeck[i] ? 1 : 0),
5466
+ tiltHang(
5467
+ mesh,
5468
+ b,
5469
+ pivot,
5470
+ (_y, i) => isNeck[i] ? 1 : 0,
5471
+ headOwnRoll(byRole.has("body"))
5472
+ ),
4977
5473
  localWarp(
4978
- import_format6.StandardParameter.Breath,
5474
+ import_format8.StandardParameter.Breath,
4979
5475
  mesh,
4980
5476
  b,
4981
5477
  [0, 1],
@@ -5022,8 +5518,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5022
5518
  if (byRole.has("mouth_open")) {
5023
5519
  warps.push(mouthWiden(mesh, b));
5024
5520
  bindings.push(
5025
- { parameter: import_format6.StandardParameter.MouthOpen, channel: "opacity", from: 1, to: 0 },
5026
- { parameter: import_format6.StandardParameter.MouthOpen, channel: "opacity", from: 1, to: 0 }
5521
+ { parameter: import_format8.StandardParameter.MouthOpen, channel: "opacity", from: 1, to: 0 },
5522
+ { parameter: import_format8.StandardParameter.MouthOpen, channel: "opacity", from: 1, to: 0 }
5027
5523
  );
5028
5524
  extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
5029
5525
  } else {
@@ -5033,19 +5529,19 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5033
5529
  );
5034
5530
  bindings.push(
5035
5531
  {
5036
- parameter: import_format6.StandardParameter.MouthOpen,
5532
+ parameter: import_format8.StandardParameter.MouthOpen,
5037
5533
  channel: "scaleY",
5038
5534
  from: 0,
5039
5535
  to: roundTo(grow, 0.01)
5040
5536
  },
5041
5537
  {
5042
- parameter: import_format6.StandardParameter.MouthOpen,
5538
+ parameter: import_format8.StandardParameter.MouthOpen,
5043
5539
  channel: "scaleX",
5044
5540
  from: 0,
5045
5541
  to: roundTo(AMPLITUDE.mouthOpenWidth - 1, 0.01)
5046
5542
  },
5047
5543
  {
5048
- parameter: import_format6.StandardParameter.MouthOpen,
5544
+ parameter: import_format8.StandardParameter.MouthOpen,
5049
5545
  channel: "translateY",
5050
5546
  from: 0,
5051
5547
  to: -roundTo(grow * bh(b) / 2, 0.01)
@@ -5057,7 +5553,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5057
5553
  case "mouth_open":
5058
5554
  warps.push(mouthForm(mesh, b), mouthOpenGrow(mesh, b));
5059
5555
  bindings.push({
5060
- parameter: import_format6.StandardParameter.MouthOpen,
5556
+ parameter: import_format8.StandardParameter.MouthOpen,
5061
5557
  channel: "opacity",
5062
5558
  from: 0,
5063
5559
  to: 1
@@ -5067,7 +5563,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5067
5563
  case "blush_L":
5068
5564
  case "blush_R":
5069
5565
  bindings.push({
5070
- parameter: import_format6.StandardParameter.Cheek,
5566
+ parameter: import_format8.StandardParameter.Cheek,
5071
5567
  channel: "opacity",
5072
5568
  from: AMPLITUDE.blushRest,
5073
5569
  to: 1
@@ -5078,8 +5574,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5078
5574
  if (byRole.has("hair_front")) {
5079
5575
  const amp = style.sway * AMPLITUDE.sway * hh;
5080
5576
  warps.push(
5081
- hairSway(import_format6.StandardParameter.HairSwayX, mesh, b, amp),
5082
- hairSway(import_format6.StandardParameter.HairSwayZ, mesh, b, amp)
5577
+ hairSway(import_format8.StandardParameter.HairSwayX, mesh, b, amp),
5578
+ hairSway(import_format8.StandardParameter.HairSwayZ, mesh, b, amp)
5083
5579
  );
5084
5580
  }
5085
5581
  warps.push(tiltHang(mesh, b, pivot, hairHangWeight(frame.chinY, b.y0)));
@@ -5152,33 +5648,24 @@ function meshScale(role, frame) {
5152
5648
  return Infinity;
5153
5649
  }
5154
5650
  }
5155
- function deformers(frame, grids, body) {
5651
+ function deformers(frame, grids, bodyWarp, arms) {
5156
5652
  const out = [];
5157
- if (body !== void 0) {
5158
- out.push({
5159
- id: "bodyDeformer",
5160
- pivot: { x: roundTo(cx(body), 0.01), y: roundTo(body.y1, 0.01) },
5161
- bindings: [
5162
- {
5163
- parameter: import_format6.StandardParameter.Breath,
5164
- channel: "translateY",
5165
- from: 0,
5166
- to: roundTo(AMPLITUDE.breathBody * frame.hh, 0.01)
5167
- }
5168
- ]
5169
- });
5170
- }
5653
+ if (bodyWarp !== void 0) out.push(bodyWarp);
5654
+ for (const [role, g] of arms) out.push(...armDeformers(role, g));
5655
+ const hasBody = bodyWarp !== void 0;
5656
+ const roll = headOwnRoll(hasBody);
5171
5657
  out.push({
5172
5658
  id: "headDeformer",
5659
+ ...hasBody ? { parent: BODY_WARP_ID } : {},
5173
5660
  pivot: { x: roundTo(frame.axisX, 0.01), y: roundTo(frame.chinY, 0.01) },
5174
5661
  bindings: [
5175
5662
  // AngleZ is clockwise-positive (Live2D's); a rotation is CCW-positive.
5176
- { parameter: import_format6.StandardParameter.AngleZ, channel: "rotate", from: ROLL_DEG, to: -ROLL_DEG },
5663
+ { parameter: import_format8.StandardParameter.AngleZ, channel: "rotate", from: roll, to: -roll },
5177
5664
  {
5178
- parameter: import_format6.StandardParameter.Breath,
5665
+ parameter: import_format8.StandardParameter.Breath,
5179
5666
  channel: "translateY",
5180
5667
  from: 0,
5181
- to: roundTo(AMPLITUDE.breathHead * frame.hh, 0.01)
5668
+ to: roundTo(headOwnBreath(hasBody) * frame.hh, 0.01)
5182
5669
  }
5183
5670
  ]
5184
5671
  });
@@ -5193,8 +5680,8 @@ function deformers(frame, grids, body) {
5193
5680
  points: g.lattice.points
5194
5681
  },
5195
5682
  warp2d: {
5196
- parameter: import_format6.StandardParameter.AngleX,
5197
- parameterY: import_format6.StandardParameter.AngleY,
5683
+ parameter: import_format8.StandardParameter.AngleX,
5684
+ parameterY: import_format8.StandardParameter.AngleY,
5198
5685
  valuesX: X_STOPS,
5199
5686
  valuesY: Y_STOPS,
5200
5687
  keyforms2d: g.keyforms.map((offsets) => ({ offsets }))
@@ -5207,16 +5694,16 @@ function hairPhysics() {
5207
5694
  return [
5208
5695
  {
5209
5696
  id: "hairSway",
5210
- input: { parameter: import_format6.StandardParameter.AngleX, weight: 1 },
5211
- output: { parameter: import_format6.StandardParameter.HairSwayX, scale: 5 },
5697
+ input: { parameter: import_format8.StandardParameter.AngleX, weight: 1 },
5698
+ output: { parameter: import_format8.StandardParameter.HairSwayX, scale: 5 },
5212
5699
  mass: 1,
5213
5700
  stiffness: 30,
5214
5701
  damping: 3
5215
5702
  },
5216
5703
  {
5217
5704
  id: "hairTilt",
5218
- input: { parameter: import_format6.StandardParameter.AngleZ, weight: 1 },
5219
- output: { parameter: import_format6.StandardParameter.HairSwayZ, scale: 8 },
5705
+ input: { parameter: import_format8.StandardParameter.AngleZ, weight: 1 },
5706
+ output: { parameter: import_format8.StandardParameter.HairSwayZ, scale: 8 },
5220
5707
  mass: 1,
5221
5708
  stiffness: 25,
5222
5709
  damping: 2.5
@@ -5226,32 +5713,46 @@ function hairPhysics() {
5226
5713
  function declareParameters(roles) {
5227
5714
  const out = [];
5228
5715
  const add = (id, name, min, max, def) => out.push({ id, name, min, max, default: def });
5229
- add(import_format6.StandardParameter.AngleX, "Head Angle", -30, 30, 0);
5230
- add(import_format6.StandardParameter.AngleY, "Head Angle Y", -30, 30, 0);
5231
- add(import_format6.StandardParameter.AngleZ, "Head Angle Z", -30, 30, 0);
5232
- add(import_format6.StandardParameter.EyeOpenLeft, "Eye L", 0, 1, 1);
5233
- add(import_format6.StandardParameter.EyeOpenRight, "Eye R", 0, 1, 1);
5716
+ add(import_format8.StandardParameter.AngleX, "Head Angle", -30, 30, 0);
5717
+ add(import_format8.StandardParameter.AngleY, "Head Angle Y", -30, 30, 0);
5718
+ add(import_format8.StandardParameter.AngleZ, "Head Angle Z", -30, 30, 0);
5719
+ if (roles.has("body")) {
5720
+ add(import_format8.StandardParameter.BodyAngleX, "Body Angle X", -10, 10, 0);
5721
+ add(import_format8.StandardParameter.BodyAngleY, "Body Angle Y", -10, 10, 0);
5722
+ add(import_format8.StandardParameter.BodyAngleZ, "Body Angle Z", -10, 10, 0);
5723
+ }
5724
+ const [armMin, armMax] = ARM_RANGE;
5725
+ if (roles.has("arm_L")) {
5726
+ add(import_format8.StandardParameter.ArmLeft, "Arm L", armMin, armMax, 0);
5727
+ add(import_format8.StandardParameter.ElbowLeft, "Elbow L", armMin, armMax, 0);
5728
+ }
5729
+ if (roles.has("arm_R")) {
5730
+ add(import_format8.StandardParameter.ArmRight, "Arm R", armMin, armMax, 0);
5731
+ add(import_format8.StandardParameter.ElbowRight, "Elbow R", armMin, armMax, 0);
5732
+ }
5733
+ add(import_format8.StandardParameter.EyeOpenLeft, "Eye L", 0, 1, 1);
5734
+ add(import_format8.StandardParameter.EyeOpenRight, "Eye R", 0, 1, 1);
5234
5735
  if ([...roles].some((r) => /^(iris|pupil|highlight)_/.test(r))) {
5235
- add(import_format6.StandardParameter.EyeballX, "Gaze X", -1, 1, 0);
5236
- add(import_format6.StandardParameter.EyeballY, "Gaze Y", -1, 1, 0);
5736
+ add(import_format8.StandardParameter.EyeballX, "Gaze X", -1, 1, 0);
5737
+ add(import_format8.StandardParameter.EyeballY, "Gaze Y", -1, 1, 0);
5237
5738
  }
5238
- add(import_format6.StandardParameter.MouthOpen, "Mouth Open", 0, 1, 0);
5239
- add(import_format6.StandardParameter.MouthForm, "Mouth Form", -1, 1, 0);
5739
+ add(import_format8.StandardParameter.MouthOpen, "Mouth Open", 0, 1, 0);
5740
+ add(import_format8.StandardParameter.MouthForm, "Mouth Form", -1, 1, 0);
5240
5741
  if (roles.has("brow_L")) {
5241
- add(import_format6.StandardParameter.BrowLeftY, "Brow L Y", -1, 1, 0);
5242
- add(import_format6.StandardParameter.BrowLeftAngle, "Brow L Angle", -1, 1, 0);
5742
+ add(import_format8.StandardParameter.BrowLeftY, "Brow L Y", -1, 1, 0);
5743
+ add(import_format8.StandardParameter.BrowLeftAngle, "Brow L Angle", -1, 1, 0);
5243
5744
  }
5244
5745
  if (roles.has("brow_R")) {
5245
- add(import_format6.StandardParameter.BrowRightY, "Brow R Y", -1, 1, 0);
5246
- add(import_format6.StandardParameter.BrowRightAngle, "Brow R Angle", -1, 1, 0);
5746
+ add(import_format8.StandardParameter.BrowRightY, "Brow R Y", -1, 1, 0);
5747
+ add(import_format8.StandardParameter.BrowRightAngle, "Brow R Angle", -1, 1, 0);
5247
5748
  }
5248
5749
  if (roles.has("blush_L") || roles.has("blush_R")) {
5249
- add(import_format6.StandardParameter.Cheek, "Cheek", 0, 1, 0);
5750
+ add(import_format8.StandardParameter.Cheek, "Cheek", 0, 1, 0);
5250
5751
  }
5251
- add(import_format6.StandardParameter.Breath, "Breath", 0, 1, 0);
5752
+ add(import_format8.StandardParameter.Breath, "Breath", 0, 1, 0);
5252
5753
  if (roles.has("hair_front")) {
5253
- add(import_format6.StandardParameter.HairSwayX, "Hair Sway X", -SWAY_RANGE, SWAY_RANGE, 0);
5254
- add(import_format6.StandardParameter.HairSwayZ, "Hair Sway Z", -SWAY_RANGE, SWAY_RANGE, 0);
5754
+ add(import_format8.StandardParameter.HairSwayX, "Hair Sway X", -SWAY_RANGE, SWAY_RANGE, 0);
5755
+ add(import_format8.StandardParameter.HairSwayZ, "Hair Sway Z", -SWAY_RANGE, SWAY_RANGE, 0);
5255
5756
  }
5256
5757
  return out;
5257
5758
  }
@@ -5270,6 +5771,7 @@ function declareParameters(roles) {
5270
5771
  DeletePhysicsRig,
5271
5772
  EditorDocument,
5272
5773
  HEAD_BAND,
5774
+ LayerGeometryError,
5273
5775
  SPECK_CORE_FRACTION,
5274
5776
  SetDeformerBindings,
5275
5777
  SetDeformerParent,
@@ -5304,6 +5806,7 @@ function declareParameters(roles) {
5304
5806
  isSpeckCore,
5305
5807
  packAtlas,
5306
5808
  parseLayerRoles,
5809
+ partIdsOfRole,
5307
5810
  upsertGridKeyform,
5308
5811
  uvRectFor,
5309
5812
  validateDeformerDelete,