@ikijs/editor 0.16.0 → 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,
@@ -721,6 +723,12 @@ var CaptureGridKeyform = class {
721
723
  prevKeyforms;
722
724
  apply(doc) {
723
725
  const deformer = doc.findWarpDeformer(this.deformerId);
726
+ const count = deformer.warps?.length ?? 0;
727
+ if (count > 1) {
728
+ throw new Error(
729
+ `deformers."${this.deformerId}".warps: ${count} grid warps \u2014 capture targets a deformer with exactly one grid warp (which entry to write into is ambiguous)`
730
+ );
731
+ }
724
732
  const warp = deformer.warps?.[0];
725
733
  if (!warp) {
726
734
  throw new Error(
@@ -1626,6 +1634,14 @@ function facePlateHalfOf(layers) {
1626
1634
  return face.cropW / 2;
1627
1635
  }
1628
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
+ ]);
1629
1645
  function createLayerSetMeasurer(canvas) {
1630
1646
  for (const field of ["width", "height"]) {
1631
1647
  const value = canvas[field];
@@ -1655,7 +1671,7 @@ function createLayerSetMeasurer(canvas) {
1655
1671
  if (bbox === null) return null;
1656
1672
  const rowLeft = new Int32Array(canvasH).fill(canvasW);
1657
1673
  const rowRight = new Int32Array(canvasH).fill(-1);
1658
- const rowRuns = role === "face" || role === "hair_front" || role === "hair_back" ? [] : void 0;
1674
+ const rowRuns = ROW_RUN_ROLES.has(role) ? [] : void 0;
1659
1675
  for (let y = 0; y < canvasH; y++) {
1660
1676
  let runs;
1661
1677
  if (rowRuns !== void 0 && y >= bbox.y && y < bbox.y + bbox.h) {
@@ -1997,7 +2013,13 @@ var PROFILE_VALUES = {
1997
2013
  brow: 0.15,
1998
2014
  breath: 0.03,
1999
2015
  sway: 0.08,
2000
- 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
2001
2023
  };
2002
2024
  function deriveProfile(v) {
2003
2025
  const farLead = v.lead * (1 - v.fore / 2);
@@ -2091,9 +2113,32 @@ function deriveProfile(v) {
2091
2113
  * slide, to the next 0.05. */
2092
2114
  hiddenNeck: Math.ceil(30 * (v.slide + v.lead) - 1e-9) / 20
2093
2115
  };
2094
- 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
+ };
2095
2140
  }
2096
- var { TURN, NOD, ROLL_DEG, AMPLITUDE } = deriveProfile(PROFILE_VALUES);
2141
+ var { TURN, NOD, ROLL_DEG, AMPLITUDE, BODY } = deriveProfile(PROFILE_VALUES);
2097
2142
  var STYLE_KNOBS = [
2098
2143
  "turn",
2099
2144
  "featureLead",
@@ -2123,6 +2168,12 @@ var TurnTargetError = class extends Error {
2123
2168
  this.name = "TurnTargetError";
2124
2169
  }
2125
2170
  };
2171
+ var LayerGeometryError = class extends Error {
2172
+ constructor(message) {
2173
+ super(message);
2174
+ this.name = "LayerGeometryError";
2175
+ }
2176
+ };
2126
2177
  var DEFAULT_TURN_TARGETS = Object.freeze({
2127
2178
  eyeShift: round2((TURN.eyeFar + TURN.eyeNear) / 2 - TURN.hairBack),
2128
2179
  farEyeRatio: round2(TURN.eyeFarScale / TURN.eyeNearScale),
@@ -2133,6 +2184,8 @@ var DEFAULT_TURN_TARGETS = Object.freeze({
2133
2184
  var ROLE_TABLE = [
2134
2185
  { role: "hair_back", family: "hair_back" },
2135
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"] },
2136
2189
  { role: "face", family: "face", required: true },
2137
2190
  { role: "blush_L", family: "face" },
2138
2191
  { role: "blush_R", family: "face" },
@@ -2164,6 +2217,15 @@ function roleSpec(role) {
2164
2217
  if (spec === void 0) throw new Error(`auto-rig: unknown role "${role}"`);
2165
2218
  return spec;
2166
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
+ }
2167
2229
  var ALIASES = {
2168
2230
  eyebrow_L: "brow_L",
2169
2231
  eyebrow_R: "brow_R",
@@ -2174,6 +2236,16 @@ function roleOf(fileName) {
2174
2236
  const role = fileName.replace(/\.[^.]+$/, "").toLowerCase().replace(/[-\s]+/g, "_").replace(/_([lr])$/, (_, s) => `_${s.toUpperCase()}`);
2175
2237
  return ALIASES[role] ?? role;
2176
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
+ }
2177
2249
  function parseLayerRoles(fileNames) {
2178
2250
  const seen = /* @__PURE__ */ new Map();
2179
2251
  const out = [];
@@ -2199,11 +2271,12 @@ function parseLayerRoles(fileNames) {
2199
2271
  `auto-rig: missing required role${missing.length > 1 ? "s" : ""} ${missing.join(", ")}`
2200
2272
  );
2201
2273
  }
2274
+ checkArmsHaveBody(seen.keys());
2202
2275
  return out;
2203
2276
  }
2204
2277
 
2205
2278
  // src/auto-rig/generate.ts
2206
- var import_format6 = require("@ikijs/format");
2279
+ var import_format8 = require("@ikijs/format");
2207
2280
 
2208
2281
  // src/auto-rig/animations.ts
2209
2282
  var import_format3 = require("@ikijs/format");
@@ -2354,6 +2427,12 @@ var DEFAULT_MOTIONS = {
2354
2427
  ]
2355
2428
  };
2356
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
+
2357
2436
  // src/auto-rig/layout.ts
2358
2437
  function boxOfLayer(layer) {
2359
2438
  return boxOfPixels(layer.bbox, layer.canvasW, layer.canvasH);
@@ -2427,6 +2506,651 @@ function meshPoints(mesh, box) {
2427
2506
  return out;
2428
2507
  }
2429
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
+
2430
3154
  // src/auto-rig/checks.ts
2431
3155
  var finite = (v) => typeof v === "number" && Number.isFinite(v);
2432
3156
  var isObject = (v) => typeof v === "object" && v !== null;
@@ -2530,6 +3254,7 @@ function checkLayers(layers, canvas) {
2530
3254
  if (missing.length > 0) {
2531
3255
  throw new Error(`auto-rig: missing required role(s) ${missing.join(", ")}`);
2532
3256
  }
3257
+ checkArmsHaveBody(seen);
2533
3258
  }
2534
3259
  function checkHeadEdges(edges, byRole) {
2535
3260
  if (edges === void 0) return;
@@ -2610,131 +3335,45 @@ function checkStrandEdges(strand, irises, hair) {
2610
3335
  for (const [k, x] of [
2611
3336
  ["irisOuter", outer],
2612
3337
  ["irisInner", inner]
2613
- ]) {
2614
- if (x < iris.x0 || x > iris.x1) {
2615
- throw new TurnTargetError(`${at}.${k} ${x} is off its iris's crop`);
2616
- }
2617
- }
2618
- if (sign * (outer - inner) <= 0) {
2619
- throw new TurnTargetError(
2620
- `${at}.irisOuter ${outer} is not strictly outward of irisInner ${inner}`
2621
- );
2622
- }
2623
- for (const [k, x] of [
2624
- ["runOuter", runOuter],
2625
- ["runFace", runFace]
2626
- ]) {
2627
- if (x !== null && (x < hair.x0 || x > hair.x1)) {
2628
- throw new TurnTargetError(`${at}.${k} ${x} is off hair_front's crop`);
2629
- }
2630
- }
2631
- if (sign * (runOuter - cx(iris)) <= 0) {
2632
- throw new TurnTargetError(
2633
- `${at}.runOuter ${runOuter} is not strictly outward of its iris's centre`
2634
- );
2635
- }
2636
- if (runFace !== null && sign * (runOuter - runFace) <= 0) {
2637
- throw new TurnTargetError(
2638
- `${at}.runOuter ${runOuter} is not strictly outward of runFace ${runFace}`
2639
- );
2640
- }
2641
- if (runFace !== null && sign * (runFace - cx(other)) <= 0) {
2642
- throw new TurnTargetError(
2643
- `${at}.runFace ${runFace} reaches the other iris's centre; a run that does is a fringe, runFace null`
2644
- );
2645
- }
2646
- }
2647
- }
2648
-
2649
- // src/auto-rig/context.ts
2650
- var import_format4 = require("@ikijs/format");
2651
-
2652
- // src/auto-rig/grid.ts
2653
- var X_STOPS = [-30, 0, 30];
2654
- var Y_STOPS = [-30, 0, 30];
2655
- function lattice(box, cell, min, max) {
2656
- const cols = cellsFor(box.x1 - box.x0, cell, min, max);
2657
- const rows = cellsFor(box.y1 - box.y0, cell, min, max);
2658
- const points = [];
2659
- for (let r = 0; r <= rows; r++) {
2660
- const y = box.y1 - (box.y1 - box.y0) * r / rows;
2661
- for (let c = 0; c <= cols; c++) {
2662
- points.push(
2663
- roundTo(box.x0 + (box.x1 - box.x0) * c / cols, 0.01),
2664
- roundTo(y, 0.01)
2665
- );
2666
- }
2667
- }
2668
- return { box, cols, rows, points };
2669
- }
2670
- function bake(l, field) {
2671
- const out = [];
2672
- for (const ay of Y_STOPS) {
2673
- for (const ax of X_STOPS) {
2674
- const offsets = [];
2675
- for (let k = 0; k < l.points.length; k += 2) {
2676
- if (ax === 0 && ay === 0) {
2677
- offsets.push(0, 0);
2678
- continue;
2679
- }
2680
- const [dx, dy] = field(l.points[k], l.points[k + 1], ax, ay);
2681
- 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`);
2682
3341
  }
2683
- out.push(offsets);
2684
3342
  }
2685
- }
2686
- return out;
2687
- }
2688
- function keyformAt(keyforms, ax) {
2689
- const j = Y_STOPS.indexOf(0);
2690
- const row = X_STOPS.map((_, i) => keyforms[j * X_STOPS.length + i]);
2691
- return blendStops(X_STOPS, row, ax);
2692
- }
2693
- function blendStops(values, offsets, v) {
2694
- const x = clamp(v, values[0], values[values.length - 1]);
2695
- let i = 0;
2696
- while (i < values.length - 2 && x > values[i + 1]) i++;
2697
- const t = (x - values[i]) / (values[i + 1] - values[i]);
2698
- return offsets[i].map((o, k) => o + (offsets[i + 1][k] - o) * t);
2699
- }
2700
- function land(l, offsets, x, y) {
2701
- const { cols, rows, points } = l;
2702
- const stride = cols + 1;
2703
- let col = cols - 1;
2704
- for (let c = 0; c < cols; c++) {
2705
- if (x < points[(c + 1) * 2]) {
2706
- col = c;
2707
- break;
3343
+ if (sign * (outer - inner) <= 0) {
3344
+ throw new TurnTargetError(
3345
+ `${at}.irisOuter ${outer} is not strictly outward of irisInner ${inner}`
3346
+ );
2708
3347
  }
2709
- }
2710
- let row = rows - 1;
2711
- for (let r = 0; r < rows; r++) {
2712
- if (y > points[(r + 1) * stride * 2 + 1]) {
2713
- row = r;
2714
- 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
+ );
2715
3370
  }
2716
3371
  }
2717
- const x0 = points[col * 2];
2718
- const x1 = points[(col + 1) * 2];
2719
- const yT = points[row * stride * 2 + 1];
2720
- const yB = points[(row + 1) * stride * 2 + 1];
2721
- const s = clamp((x - x0) / (x1 - x0), 0, 1);
2722
- const t = clamp((yT - y) / (yT - yB), 0, 1);
2723
- const at = (r, c) => {
2724
- const k = (r * stride + c) * 2;
2725
- return [points[k] + offsets[k], points[k + 1] + offsets[k + 1]];
2726
- };
2727
- const p00 = at(row, col);
2728
- const p10 = at(row, col + 1);
2729
- const p01 = at(row + 1, col);
2730
- const p11 = at(row + 1, col + 1);
2731
- const topX = p00[0] + (p10[0] - p00[0]) * s;
2732
- const topY = p00[1] + (p10[1] - p00[1]) * s;
2733
- const botX = p01[0] + (p11[0] - p01[0]) * s;
2734
- const botY = p01[1] + (p11[1] - p01[1]) * s;
2735
- return [topX + (botX - topX) * t, topY + (botY - topY) * t];
2736
3372
  }
2737
3373
 
3374
+ // src/auto-rig/context.ts
3375
+ var import_format7 = require("@ikijs/format");
3376
+
2738
3377
  // src/auto-rig/head.ts
2739
3378
  function outerEdge(runs, axisX, side) {
2740
3379
  if (runs.length === 0) return void 0;
@@ -3764,6 +4403,7 @@ function familyField(m, family) {
3764
4403
  case "hair_back":
3765
4404
  return hairField(m, m.hairBack, NOD.hairBack);
3766
4405
  case "body":
4406
+ case "arm":
3767
4407
  return () => [0, 0];
3768
4408
  }
3769
4409
  }
@@ -3863,7 +4503,7 @@ function renderedLander(turn, parts, grids, from = /* @__PURE__ */ new Map()) {
3863
4503
  const family = roleSpec(role).family;
3864
4504
  const part = parts.find((p) => p.id === role);
3865
4505
  const g = grids.get(family);
3866
- 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);
3867
4507
  if (part === void 0 || part.mesh === void 0 || !g && !warp) {
3868
4508
  return x + field(role)(x, y, ax, 0)[0];
3869
4509
  }
@@ -4572,177 +5212,6 @@ function buildTurn(frame, landmarks, q) {
4572
5212
  };
4573
5213
  }
4574
5214
 
4575
- // src/auto-rig/drivers.ts
4576
- var import_format5 = require("@ikijs/format");
4577
- var SWAY_RANGE = 20;
4578
- var SWAY_CURVE = 1.4;
4579
- var HAIR_HANG = 0.35;
4580
- var Z_STOPS = [-30, 0, 30];
4581
- function localWarp(parameter, mesh, box, values, f) {
4582
- const pts = meshPoints(mesh, box);
4583
- const w = bw(box);
4584
- const h = bh(box);
4585
- return {
4586
- parameter,
4587
- keyforms: values.map((value) => ({
4588
- value,
4589
- offsets: pts.flatMap((p, i) => {
4590
- const [dx, dy] = f(p, value, i);
4591
- return [roundTo(dx / w, 1e-4), roundTo(dy / h, 1e-4)];
4592
- })
4593
- }))
4594
- };
4595
- }
4596
- var rollOf = (v) => -v / 30 * ROLL_DEG * DEG;
4597
- function motionReach(warps, mesh, box, extra = [0, 0]) {
4598
- const out = {
4599
- left: extra[0],
4600
- right: extra[0],
4601
- bottom: extra[1],
4602
- top: extra[1]
4603
- };
4604
- if (mesh === void 0) return out;
4605
- const pts = meshPoints(mesh, box);
4606
- for (const warp of warps ?? []) {
4607
- let l = 0;
4608
- let r = 0;
4609
- let b = 0;
4610
- let t = 0;
4611
- for (const k of warp.keyforms) {
4612
- pts.forEach(([x, y], i) => {
4613
- const px = x + k.offsets[2 * i] * bw(box);
4614
- const py = y + k.offsets[2 * i + 1] * bh(box);
4615
- l = Math.max(l, box.x0 - px);
4616
- r = Math.max(r, px - box.x1);
4617
- b = Math.max(b, box.y0 - py);
4618
- t = Math.max(t, py - box.y1);
4619
- });
4620
- }
4621
- out.left += l;
4622
- out.right += r;
4623
- out.bottom += b;
4624
- out.top += t;
4625
- }
4626
- return out;
4627
- }
4628
- function blinkFold(param, mesh, box, travel, eye = box) {
4629
- const yc = eye.y1 - travel * bh(eye);
4630
- return localWarp(
4631
- param,
4632
- mesh,
4633
- box,
4634
- [0, 1],
4635
- ([, y], v) => v === 1 ? [0, 0] : [0, yc - y]
4636
- );
4637
- }
4638
- function lashFold(param, mesh, lash, eye, travel) {
4639
- const yc = eye.y1 - travel * bh(eye) - 0.04 * bh(eye);
4640
- const c = cx(lash);
4641
- const half = bw(lash) / 2;
4642
- const sag = 0.3 * bh(lash);
4643
- return localWarp(param, mesh, lash, [0, 1], ([x, y], v) => {
4644
- if (v === 1) return [0, 0];
4645
- const u = (x - c) / half;
4646
- return [0, yc + (y - lash.y0) * 0.5 - sag * (1 - u * u) - y];
4647
- });
4648
- }
4649
- function gazeBindings(irisW, share = 1) {
4650
- const gx = AMPLITUDE.gazeX * irisW * share;
4651
- const gy = AMPLITUDE.gazeY * irisW * share;
4652
- return [
4653
- {
4654
- parameter: import_format5.StandardParameter.EyeballX,
4655
- channel: "translateX",
4656
- from: -round1(gx),
4657
- to: round1(gx)
4658
- },
4659
- {
4660
- parameter: import_format5.StandardParameter.EyeballY,
4661
- channel: "translateY",
4662
- from: -round1(gy),
4663
- to: round1(gy)
4664
- }
4665
- ];
4666
- }
4667
- function gazeReach(irisW, share = 1) {
4668
- return [AMPLITUDE.gazeX * irisW * share, AMPLITUDE.gazeY * irisW * share];
4669
- }
4670
- function browReach(hh) {
4671
- return AMPLITUDE.brow * hh;
4672
- }
4673
- function browBindings(side, reach) {
4674
- const y = side === "L" ? import_format5.StandardParameter.BrowLeftY : import_format5.StandardParameter.BrowRightY;
4675
- const a = side === "L" ? import_format5.StandardParameter.BrowLeftAngle : import_format5.StandardParameter.BrowRightAngle;
4676
- return [
4677
- {
4678
- parameter: y,
4679
- channel: "translateY",
4680
- from: -round1(reach),
4681
- to: round1(reach)
4682
- },
4683
- { parameter: a, channel: "rotate", from: -BROW_TILT, to: BROW_TILT }
4684
- ];
4685
- }
4686
- var BROW_TILT = 15;
4687
- function mouthForm(mesh, box) {
4688
- const a = 0.3 * bh(box);
4689
- const c = cx(box);
4690
- const half = bw(box) / 2;
4691
- return localWarp(import_format5.StandardParameter.MouthForm, mesh, box, [-1, 0, 1], ([x], v) => {
4692
- if (v === 0) return [0, 0];
4693
- const u = (x - c) / half;
4694
- const widen = v > 0 ? 0.05 : 0.03;
4695
- return [v * widen * (x - c), v * a * (u * u - 0.3)];
4696
- });
4697
- }
4698
- function mouthOpenGrow(mesh, box) {
4699
- const c = cx(box);
4700
- return localWarp(
4701
- import_format5.StandardParameter.MouthOpen,
4702
- mesh,
4703
- box,
4704
- [0, 1],
4705
- ([x, y], v) => v === 1 ? [(AMPLITUDE.mouthOpenWidth - 1) * (x - c), 0] : [0, box.y1 + (y - box.y1) * 0.2 - y]
4706
- );
4707
- }
4708
- function mouthWiden(mesh, box) {
4709
- const c = cx(box);
4710
- return localWarp(import_format5.StandardParameter.MouthOpen, mesh, box, [0, 1], ([x], v) => [
4711
- v * (AMPLITUDE.mouthOpenWidth - 1) * (x - c),
4712
- 0
4713
- ]);
4714
- }
4715
- function hairSway(param, mesh, box, amp) {
4716
- return localWarp(
4717
- param,
4718
- mesh,
4719
- box,
4720
- [-SWAY_RANGE, 0, SWAY_RANGE],
4721
- ([, y], v) => {
4722
- const t = Math.max(0, (box.y1 - y) / bh(box));
4723
- return [v / SWAY_RANGE * amp * t ** SWAY_CURVE, 0];
4724
- }
4725
- );
4726
- }
4727
- function tiltHang(mesh, box, pivot, weight) {
4728
- return localWarp(import_format5.StandardParameter.AngleZ, mesh, box, Z_STOPS, ([x, y], v, i) => {
4729
- const g = weight(y, i);
4730
- if (g === 0 || v === 0) return [0, 0];
4731
- const a = -g * rollOf(v);
4732
- const px = x - pivot[0];
4733
- const py = y - pivot[1];
4734
- return [
4735
- pivot[0] + px * Math.cos(a) - py * Math.sin(a) - x,
4736
- pivot[1] + px * Math.sin(a) + py * Math.cos(a) - y
4737
- ];
4738
- });
4739
- }
4740
- function hairHangWeight(pivotY, bottom) {
4741
- if (bottom >= pivotY) return () => 0;
4742
- return (y) => HAIR_HANG * smoothstep(pivotY, bottom, y);
4743
- }
4744
- var round1 = (v) => roundTo(v, 0.1);
4745
-
4746
5215
  // src/auto-rig/generate.ts
4747
5216
  var MESH_CELLS = {
4748
5217
  face: { px: 28, min: 4, max: 24 },
@@ -4769,6 +5238,7 @@ var WARP_ID = {
4769
5238
  mouth: "mouthWarp"
4770
5239
  };
4771
5240
  var isGridFamily = (f) => f in WARP_ID;
5241
+ var isArm = (spec) => spec.family === "arm";
4772
5242
  var GRID_PAD = 6;
4773
5243
  function generateIkiFromLayerSet(layers, canvas, options = {}) {
4774
5244
  checkLayers(layers, canvas);
@@ -4817,6 +5287,18 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4817
5287
  chinSlide: chinSlide(fit.model)
4818
5288
  });
4819
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;
4820
5302
  const parts = [];
4821
5303
  const reach = /* @__PURE__ */ new Map();
4822
5304
  let regionOf = [];
@@ -4824,6 +5306,12 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4824
5306
  for (const spec of ROLE_TABLE) {
4825
5307
  const layer = byRole.get(spec.role);
4826
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
+ }
4827
5315
  const built = buildPart(
4828
5316
  spec,
4829
5317
  boxOfLayer(layer),
@@ -4842,7 +5330,9 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4842
5330
  }
4843
5331
  const grids = /* @__PURE__ */ new Map();
4844
5332
  for (const family of Object.keys(WARP_ID)) {
4845
- const members = parts.filter((p) => roleSpec(p.id).family === family);
5333
+ const members = parts.filter(
5334
+ (p) => roleSpec(roleOfPart(p.id)).family === family
5335
+ );
4846
5336
  if (members.length === 0) continue;
4847
5337
  const area = unionBoxes(
4848
5338
  members.map((p) => {
@@ -4864,13 +5354,19 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4864
5354
  });
4865
5355
  }
4866
5356
  for (const part of parts) {
4867
- const family = roleSpec(part.id).family;
5357
+ const family = roleSpec(roleOfPart(part.id)).family;
4868
5358
  if (isGridFamily(family)) {
4869
5359
  part.deformer = WARP_ID[family];
4870
5360
  continue;
4871
5361
  }
4872
- part.deformer = family === "body" ? "bodyDeformer" : "headDeformer";
4873
- 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;
4874
5370
  const own = familyField(turn, family);
4875
5371
  const fieldOf = part.id === "face" ? plateFields(turn, regionOf, own) : () => own;
4876
5372
  part.warps = [
@@ -4880,17 +5376,17 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4880
5376
  }
4881
5377
  const parameters = declareParameters(new Set(byRole.keys()));
4882
5378
  const model = {
4883
- version: import_format6.IKI_FORMAT_VERSION,
5379
+ version: import_format8.IKI_FORMAT_VERSION,
4884
5380
  name: "auto-rigged",
4885
5381
  canvas: { width: canvas.width, height: canvas.height },
4886
5382
  parameters,
4887
5383
  parts,
4888
- deformers: deformers(frame, grids, has("body") ? box("body") : void 0),
5384
+ deformers: deformers(frame, grids, bodyWarp?.deformer, arms),
4889
5385
  ...has("hair_front") ? { physics: hairPhysics() } : {},
4890
5386
  expressions: defaultExpressions(new Set(parameters.map((p) => p.id))),
4891
5387
  motions: DEFAULT_MOTIONS
4892
5388
  };
4893
- const valid = (0, import_format6.parseIkiModel)(model);
5389
+ const valid = (0, import_format8.parseIkiModel)(model);
4894
5390
  if (hasNose && options.onTurnSolved) {
4895
5391
  options.onTurnSolved(
4896
5392
  turnReport(
@@ -4905,14 +5401,14 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4905
5401
  function turnWarps(mesh, b, fieldOf) {
4906
5402
  return [
4907
5403
  localWarp(
4908
- import_format6.StandardParameter.AngleX,
5404
+ import_format8.StandardParameter.AngleX,
4909
5405
  mesh,
4910
5406
  b,
4911
5407
  X_STOPS,
4912
5408
  ([x, y], v, i) => v === 0 ? [0, 0] : [fieldOf(i)(x, y, v, 0)[0], 0]
4913
5409
  ),
4914
5410
  localWarp(
4915
- import_format6.StandardParameter.AngleY,
5411
+ import_format8.StandardParameter.AngleY,
4916
5412
  mesh,
4917
5413
  b,
4918
5414
  Y_STOPS,
@@ -4960,16 +5456,22 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
4960
5456
  }
4961
5457
  if (mesh !== void 0) part.mesh = mesh;
4962
5458
  const side = spec.role.endsWith("_L") ? "L" : "R";
4963
- 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;
4964
5460
  const hh = frame.hh;
4965
5461
  switch (spec.role) {
4966
5462
  case "face":
4967
5463
  if (region?.some((r) => r === "neck")) {
4968
5464
  const isNeck = region.map((r) => r === "neck");
4969
5465
  warps.push(
4970
- 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
+ ),
4971
5473
  localWarp(
4972
- import_format6.StandardParameter.Breath,
5474
+ import_format8.StandardParameter.Breath,
4973
5475
  mesh,
4974
5476
  b,
4975
5477
  [0, 1],
@@ -5016,8 +5518,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5016
5518
  if (byRole.has("mouth_open")) {
5017
5519
  warps.push(mouthWiden(mesh, b));
5018
5520
  bindings.push(
5019
- { parameter: import_format6.StandardParameter.MouthOpen, channel: "opacity", from: 1, to: 0 },
5020
- { 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 }
5021
5523
  );
5022
5524
  extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
5023
5525
  } else {
@@ -5027,19 +5529,19 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5027
5529
  );
5028
5530
  bindings.push(
5029
5531
  {
5030
- parameter: import_format6.StandardParameter.MouthOpen,
5532
+ parameter: import_format8.StandardParameter.MouthOpen,
5031
5533
  channel: "scaleY",
5032
5534
  from: 0,
5033
5535
  to: roundTo(grow, 0.01)
5034
5536
  },
5035
5537
  {
5036
- parameter: import_format6.StandardParameter.MouthOpen,
5538
+ parameter: import_format8.StandardParameter.MouthOpen,
5037
5539
  channel: "scaleX",
5038
5540
  from: 0,
5039
5541
  to: roundTo(AMPLITUDE.mouthOpenWidth - 1, 0.01)
5040
5542
  },
5041
5543
  {
5042
- parameter: import_format6.StandardParameter.MouthOpen,
5544
+ parameter: import_format8.StandardParameter.MouthOpen,
5043
5545
  channel: "translateY",
5044
5546
  from: 0,
5045
5547
  to: -roundTo(grow * bh(b) / 2, 0.01)
@@ -5051,7 +5553,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5051
5553
  case "mouth_open":
5052
5554
  warps.push(mouthForm(mesh, b), mouthOpenGrow(mesh, b));
5053
5555
  bindings.push({
5054
- parameter: import_format6.StandardParameter.MouthOpen,
5556
+ parameter: import_format8.StandardParameter.MouthOpen,
5055
5557
  channel: "opacity",
5056
5558
  from: 0,
5057
5559
  to: 1
@@ -5061,7 +5563,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5061
5563
  case "blush_L":
5062
5564
  case "blush_R":
5063
5565
  bindings.push({
5064
- parameter: import_format6.StandardParameter.Cheek,
5566
+ parameter: import_format8.StandardParameter.Cheek,
5065
5567
  channel: "opacity",
5066
5568
  from: AMPLITUDE.blushRest,
5067
5569
  to: 1
@@ -5072,8 +5574,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5072
5574
  if (byRole.has("hair_front")) {
5073
5575
  const amp = style.sway * AMPLITUDE.sway * hh;
5074
5576
  warps.push(
5075
- hairSway(import_format6.StandardParameter.HairSwayX, mesh, b, amp),
5076
- 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)
5077
5579
  );
5078
5580
  }
5079
5581
  warps.push(tiltHang(mesh, b, pivot, hairHangWeight(frame.chinY, b.y0)));
@@ -5146,33 +5648,24 @@ function meshScale(role, frame) {
5146
5648
  return Infinity;
5147
5649
  }
5148
5650
  }
5149
- function deformers(frame, grids, body) {
5651
+ function deformers(frame, grids, bodyWarp, arms) {
5150
5652
  const out = [];
5151
- if (body !== void 0) {
5152
- out.push({
5153
- id: "bodyDeformer",
5154
- pivot: { x: roundTo(cx(body), 0.01), y: roundTo(body.y1, 0.01) },
5155
- bindings: [
5156
- {
5157
- parameter: import_format6.StandardParameter.Breath,
5158
- channel: "translateY",
5159
- from: 0,
5160
- to: roundTo(AMPLITUDE.breathBody * frame.hh, 0.01)
5161
- }
5162
- ]
5163
- });
5164
- }
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);
5165
5657
  out.push({
5166
5658
  id: "headDeformer",
5659
+ ...hasBody ? { parent: BODY_WARP_ID } : {},
5167
5660
  pivot: { x: roundTo(frame.axisX, 0.01), y: roundTo(frame.chinY, 0.01) },
5168
5661
  bindings: [
5169
5662
  // AngleZ is clockwise-positive (Live2D's); a rotation is CCW-positive.
5170
- { 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 },
5171
5664
  {
5172
- parameter: import_format6.StandardParameter.Breath,
5665
+ parameter: import_format8.StandardParameter.Breath,
5173
5666
  channel: "translateY",
5174
5667
  from: 0,
5175
- to: roundTo(AMPLITUDE.breathHead * frame.hh, 0.01)
5668
+ to: roundTo(headOwnBreath(hasBody) * frame.hh, 0.01)
5176
5669
  }
5177
5670
  ]
5178
5671
  });
@@ -5187,8 +5680,8 @@ function deformers(frame, grids, body) {
5187
5680
  points: g.lattice.points
5188
5681
  },
5189
5682
  warp2d: {
5190
- parameter: import_format6.StandardParameter.AngleX,
5191
- parameterY: import_format6.StandardParameter.AngleY,
5683
+ parameter: import_format8.StandardParameter.AngleX,
5684
+ parameterY: import_format8.StandardParameter.AngleY,
5192
5685
  valuesX: X_STOPS,
5193
5686
  valuesY: Y_STOPS,
5194
5687
  keyforms2d: g.keyforms.map((offsets) => ({ offsets }))
@@ -5201,16 +5694,16 @@ function hairPhysics() {
5201
5694
  return [
5202
5695
  {
5203
5696
  id: "hairSway",
5204
- input: { parameter: import_format6.StandardParameter.AngleX, weight: 1 },
5205
- 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 },
5206
5699
  mass: 1,
5207
5700
  stiffness: 30,
5208
5701
  damping: 3
5209
5702
  },
5210
5703
  {
5211
5704
  id: "hairTilt",
5212
- input: { parameter: import_format6.StandardParameter.AngleZ, weight: 1 },
5213
- 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 },
5214
5707
  mass: 1,
5215
5708
  stiffness: 25,
5216
5709
  damping: 2.5
@@ -5220,32 +5713,46 @@ function hairPhysics() {
5220
5713
  function declareParameters(roles) {
5221
5714
  const out = [];
5222
5715
  const add = (id, name, min, max, def) => out.push({ id, name, min, max, default: def });
5223
- add(import_format6.StandardParameter.AngleX, "Head Angle", -30, 30, 0);
5224
- add(import_format6.StandardParameter.AngleY, "Head Angle Y", -30, 30, 0);
5225
- add(import_format6.StandardParameter.AngleZ, "Head Angle Z", -30, 30, 0);
5226
- add(import_format6.StandardParameter.EyeOpenLeft, "Eye L", 0, 1, 1);
5227
- 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);
5228
5735
  if ([...roles].some((r) => /^(iris|pupil|highlight)_/.test(r))) {
5229
- add(import_format6.StandardParameter.EyeballX, "Gaze X", -1, 1, 0);
5230
- 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);
5231
5738
  }
5232
- add(import_format6.StandardParameter.MouthOpen, "Mouth Open", 0, 1, 0);
5233
- 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);
5234
5741
  if (roles.has("brow_L")) {
5235
- add(import_format6.StandardParameter.BrowLeftY, "Brow L Y", -1, 1, 0);
5236
- 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);
5237
5744
  }
5238
5745
  if (roles.has("brow_R")) {
5239
- add(import_format6.StandardParameter.BrowRightY, "Brow R Y", -1, 1, 0);
5240
- 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);
5241
5748
  }
5242
5749
  if (roles.has("blush_L") || roles.has("blush_R")) {
5243
- add(import_format6.StandardParameter.Cheek, "Cheek", 0, 1, 0);
5750
+ add(import_format8.StandardParameter.Cheek, "Cheek", 0, 1, 0);
5244
5751
  }
5245
- add(import_format6.StandardParameter.Breath, "Breath", 0, 1, 0);
5752
+ add(import_format8.StandardParameter.Breath, "Breath", 0, 1, 0);
5246
5753
  if (roles.has("hair_front")) {
5247
- add(import_format6.StandardParameter.HairSwayX, "Hair Sway X", -SWAY_RANGE, SWAY_RANGE, 0);
5248
- 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);
5249
5756
  }
5250
5757
  return out;
5251
5758
  }
@@ -5264,6 +5771,7 @@ function declareParameters(roles) {
5264
5771
  DeletePhysicsRig,
5265
5772
  EditorDocument,
5266
5773
  HEAD_BAND,
5774
+ LayerGeometryError,
5267
5775
  SPECK_CORE_FRACTION,
5268
5776
  SetDeformerBindings,
5269
5777
  SetDeformerParent,
@@ -5298,6 +5806,7 @@ function declareParameters(roles) {
5298
5806
  isSpeckCore,
5299
5807
  packAtlas,
5300
5808
  parseLayerRoles,
5809
+ partIdsOfRole,
5301
5810
  upsertGridKeyform,
5302
5811
  uvRectFor,
5303
5812
  validateDeformerDelete,