@ikijs/editor 0.17.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -2105,267 +2105,14 @@ var DEFAULT_TURN_TARGETS = Object.freeze({
2105
2105
  silhouetteRatio: 1
2106
2106
  });
2107
2107
 
2108
- // src/auto-rig/roles.ts
2109
- var ROLE_TABLE = [
2110
- { role: "hair_back", family: "hair_back" },
2111
- { role: "body", family: "body" },
2112
- { role: "arm_L", family: "arm", parts: ["arm_L", "forearm_L"] },
2113
- { role: "arm_R", family: "arm", parts: ["arm_R", "forearm_R"] },
2114
- { role: "face", family: "face", required: true },
2115
- { role: "blush_L", family: "face" },
2116
- { role: "blush_R", family: "face" },
2117
- { role: "nose", family: "nose" },
2118
- { role: "mouth", family: "mouth", required: true },
2119
- { role: "mouth_open", family: "mouth" },
2120
- { role: "eye_L", family: "eye_L", required: true },
2121
- { role: "eye_R", family: "eye_R", required: true },
2122
- { role: "iris_L", family: "eye_L" },
2123
- { role: "iris_R", family: "eye_R" },
2124
- { role: "pupil_L", family: "eye_L" },
2125
- { role: "pupil_R", family: "eye_R" },
2126
- { role: "highlight_L", family: "eye_L" },
2127
- { role: "highlight_R", family: "eye_R" },
2128
- { role: "lash_lower_L", family: "eye_L" },
2129
- { role: "lash_lower_R", family: "eye_R" },
2130
- { role: "lash_L", family: "eye_L" },
2131
- { role: "lash_R", family: "eye_R" },
2132
- { role: "brow_L", family: "brow_L" },
2133
- { role: "brow_R", family: "brow_R" },
2134
- { role: "hair_front", family: "hair_front" }
2135
- ];
2136
- var REQUIRED_ROLES = ROLE_TABLE.filter(
2137
- (r) => r.required
2138
- ).map((r) => r.role);
2139
- var SPEC_BY_ROLE = new Map(ROLE_TABLE.map((r, i) => [r.role, { ...r, i }]));
2140
- function roleSpec(role) {
2141
- const spec = SPEC_BY_ROLE.get(role);
2142
- if (spec === void 0) throw new Error(`auto-rig: unknown role "${role}"`);
2143
- return spec;
2144
- }
2145
- function partIdsOfRole(role) {
2146
- return roleSpec(role).parts ?? [role];
2147
- }
2148
- var ROLE_BY_PART = new Map(
2149
- ROLE_TABLE.flatMap((r) => (r.parts ?? []).map((p) => [p, r.role]))
2150
- );
2151
- function roleOfPart(partId) {
2152
- return ROLE_BY_PART.get(partId) ?? partId;
2153
- }
2154
- var ALIASES = {
2155
- eyebrow_L: "brow_L",
2156
- eyebrow_R: "brow_R",
2157
- eye_white_L: "eye_L",
2158
- eye_white_R: "eye_R"
2159
- };
2160
- function roleOf(fileName) {
2161
- const role = fileName.replace(/\.[^.]+$/, "").toLowerCase().replace(/[-\s]+/g, "_").replace(/_([lr])$/, (_, s) => `_${s.toUpperCase()}`);
2162
- return ALIASES[role] ?? role;
2163
- }
2164
- function checkArmsHaveBody(roles) {
2165
- const has = new Set(roles);
2166
- if (has.has("body")) return;
2167
- const arm = ROLE_TABLE.find((r) => r.family === "arm" && has.has(r.role));
2168
- if (arm !== void 0) {
2169
- throw new Error(
2170
- `auto-rig: ${arm.role} needs a body layer (an arm hangs from the body's shoulder)`
2171
- );
2172
- }
2173
- }
2174
- function parseLayerRoles(fileNames) {
2175
- const seen = /* @__PURE__ */ new Map();
2176
- const out = [];
2177
- for (const fileName of fileNames) {
2178
- const role = roleOf(fileName);
2179
- if (!SPEC_BY_ROLE.has(role)) {
2180
- throw new Error(
2181
- `auto-rig: "${fileName}" names no known role (expected one of ${ROLE_TABLE.map((r) => r.role).join(", ")})`
2182
- );
2183
- }
2184
- const prior = seen.get(role);
2185
- if (prior !== void 0) {
2186
- throw new Error(
2187
- `auto-rig: role "${role}" is named twice ("${prior}" and "${fileName}")`
2188
- );
2189
- }
2190
- seen.set(role, fileName);
2191
- out.push({ role, fileName });
2192
- }
2193
- const missing = REQUIRED_ROLES.filter((r) => !seen.has(r));
2194
- if (missing.length > 0) {
2195
- throw new Error(
2196
- `auto-rig: missing required role${missing.length > 1 ? "s" : ""} ${missing.join(", ")}`
2197
- );
2198
- }
2199
- checkArmsHaveBody(seen.keys());
2200
- return out;
2201
- }
2202
-
2203
- // src/auto-rig/generate.ts
2204
- import {
2205
- IKI_FORMAT_VERSION as IKI_FORMAT_VERSION2,
2206
- StandardParameter as P6,
2207
- parseIkiModel as parseIkiModel3
2208
- } from "@ikijs/format";
2209
-
2210
- // src/auto-rig/animations.ts
2211
- import {
2212
- StandardParameter as P
2213
- } from "@ikijs/format";
2214
- function browsY(y) {
2215
- return [
2216
- { parameter: P.BrowLeftY, value: y, blend: "add" },
2217
- { parameter: P.BrowRightY, value: y, blend: "add" }
2218
- ];
2219
- }
2220
- function browsTilt(innerDown) {
2221
- return [
2222
- { parameter: P.BrowLeftAngle, value: innerDown, blend: "add" },
2223
- { parameter: P.BrowRightAngle, value: -innerDown, blend: "add" }
2224
- ];
2225
- }
2226
- function cheek(value) {
2227
- return { parameter: P.Cheek, value, blend: "add" };
2228
- }
2229
- var BROWS = [P.BrowLeftY, P.BrowRightY, P.BrowLeftAngle, P.BrowRightAngle];
2230
- var EXPRESSIONS = [
2231
- {
2232
- id: "smile",
2233
- description: "Smiling and pleased: warm, friendly, content. For greetings, thanks, agreement and gentle happiness.",
2234
- parameters: [
2235
- { parameter: P.MouthForm, value: 1, blend: "add" },
2236
- ...browsY(0.25),
2237
- cheek(0.4)
2238
- ]
2239
- },
2240
- {
2241
- id: "laugh",
2242
- description: "Laughing, eyes shut and mouth open: delighted, amused. For jokes, playfulness and big joy.",
2243
- parameters: [
2244
- { parameter: P.EyeOpenLeft, value: 0, blend: "multiply" },
2245
- { parameter: P.EyeOpenRight, value: 0, blend: "multiply" },
2246
- { parameter: P.MouthForm, value: 1, blend: "add" },
2247
- { parameter: P.MouthOpen, value: 0.8, blend: "overwrite" },
2248
- ...browsY(0.4),
2249
- cheek(0.65)
2250
- ]
2251
- },
2252
- {
2253
- id: "angry",
2254
- description: "Angry or annoyed, frowning. For irritation, frustration, indignation or scolding.",
2255
- needs: BROWS,
2256
- parameters: [
2257
- ...browsY(-0.5),
2258
- ...browsTilt(0.8),
2259
- { parameter: P.MouthForm, value: -0.9, blend: "add" }
2260
- ]
2261
- },
2262
- {
2263
- id: "sad",
2264
- description: "Sad or disappointed, downcast. For sorrow, regret, apology or sympathy.",
2265
- needs: BROWS,
2266
- parameters: [
2267
- ...browsY(0.35),
2268
- ...browsTilt(-0.8),
2269
- { parameter: P.MouthForm, value: -0.8, blend: "add" }
2270
- ]
2271
- },
2272
- {
2273
- id: "surprised",
2274
- description: "Surprised, mouth dropped open: startled, amazed. For shock, sudden news or disbelief.",
2275
- parameters: [
2276
- ...browsY(0.8),
2277
- { parameter: P.MouthOpen, value: 0.9, blend: "overwrite" }
2278
- ]
2279
- },
2280
- {
2281
- id: "shy",
2282
- description: "Shy or embarrassed: bashful, flustered. For being praised, teased or caught off guard.",
2283
- needs: [P.Cheek],
2284
- parameters: [
2285
- cheek(1),
2286
- // Gaze down.
2287
- { parameter: P.EyeballY, value: -0.65, blend: "add" },
2288
- { parameter: P.MouthForm, value: 0.4, blend: "add" },
2289
- ...browsTilt(-0.25)
2290
- ]
2291
- }
2292
- ];
2293
- function defaultExpressions(declared) {
2294
- return EXPRESSIONS.filter(
2295
- (e) => e.needs === void 0 || e.needs.some((id) => declared.has(id))
2296
- ).map(({ needs: _needs, ...e }) => ({
2297
- ...e,
2298
- parameters: e.parameters.filter((t) => declared.has(t.parameter))
2299
- }));
2300
- }
2301
- var DEFAULT_MOTIONS = {
2302
- Nod: [
2303
- {
2304
- description: "Nods yes: agreement, acknowledgement, understanding.",
2305
- duration: 1,
2306
- curves: [
2307
- {
2308
- parameter: P.AngleY,
2309
- keys: [
2310
- [0, 0],
2311
- [0.3, -18],
2312
- [0.6, 4],
2313
- [0.8, -1],
2314
- [1, 0]
2315
- ]
2316
- }
2317
- ]
2318
- }
2319
- ],
2320
- Shake: [
2321
- {
2322
- description: "Shakes the head no: disagreement, refusal, disbelief.",
2323
- duration: 1.2,
2324
- // The first swing peaks at 0.2 s, which the default fade would still
2325
- // be damping.
2326
- fadeIn: 0.15,
2327
- curves: [
2328
- {
2329
- parameter: P.AngleX,
2330
- keys: [
2331
- [0, 0],
2332
- [0.2, -18],
2333
- [0.45, 18],
2334
- [0.7, -16],
2335
- [0.95, 10],
2336
- [1.2, 0]
2337
- ]
2338
- }
2339
- ]
2340
- }
2341
- ],
2342
- Tilt: [
2343
- {
2344
- description: "Tilts the head to one side and back: curiosity, puzzlement, a question.",
2345
- duration: 1.4,
2346
- curves: [
2347
- {
2348
- parameter: P.AngleZ,
2349
- keys: [
2350
- [0, 0],
2351
- [0.35, 20],
2352
- [1, 20],
2353
- [1.4, 0]
2354
- ]
2355
- }
2356
- ]
2357
- }
2358
- ]
2359
- };
2360
-
2361
2108
  // src/auto-rig/arms.ts
2362
2109
  import {
2363
- StandardParameter as P4
2110
+ StandardParameter as P3
2364
2111
  } from "@ikijs/format";
2365
2112
 
2366
2113
  // src/auto-rig/body.ts
2367
2114
  import {
2368
- StandardParameter as P3
2115
+ StandardParameter as P2
2369
2116
  } from "@ikijs/format";
2370
2117
 
2371
2118
  // src/auto-rig/layout.ts
@@ -2443,7 +2190,7 @@ function meshPoints(mesh, box) {
2443
2190
 
2444
2191
  // src/auto-rig/drivers.ts
2445
2192
  import {
2446
- StandardParameter as P2
2193
+ StandardParameter as P
2447
2194
  } from "@ikijs/format";
2448
2195
  var SWAY_RANGE = 20;
2449
2196
  var SWAY_CURVE = 1.4;
@@ -2522,13 +2269,13 @@ function gazeBindings(irisW, share = 1) {
2522
2269
  const gy = AMPLITUDE.gazeY * irisW * share;
2523
2270
  return [
2524
2271
  {
2525
- parameter: P2.EyeballX,
2272
+ parameter: P.EyeballX,
2526
2273
  channel: "translateX",
2527
2274
  from: -round1(gx),
2528
2275
  to: round1(gx)
2529
2276
  },
2530
2277
  {
2531
- parameter: P2.EyeballY,
2278
+ parameter: P.EyeballY,
2532
2279
  channel: "translateY",
2533
2280
  from: -round1(gy),
2534
2281
  to: round1(gy)
@@ -2542,8 +2289,8 @@ function browReach(hh) {
2542
2289
  return AMPLITUDE.brow * hh;
2543
2290
  }
2544
2291
  function browBindings(side, reach) {
2545
- const y = side === "L" ? P2.BrowLeftY : P2.BrowRightY;
2546
- const a = side === "L" ? P2.BrowLeftAngle : P2.BrowRightAngle;
2292
+ const y = side === "L" ? P.BrowLeftY : P.BrowRightY;
2293
+ const a = side === "L" ? P.BrowLeftAngle : P.BrowRightAngle;
2547
2294
  return [
2548
2295
  {
2549
2296
  parameter: y,
@@ -2559,7 +2306,7 @@ function mouthForm(mesh, box) {
2559
2306
  const a = 0.3 * bh(box);
2560
2307
  const c = cx(box);
2561
2308
  const half = bw(box) / 2;
2562
- return localWarp(P2.MouthForm, mesh, box, [-1, 0, 1], ([x], v) => {
2309
+ return localWarp(P.MouthForm, mesh, box, [-1, 0, 1], ([x], v) => {
2563
2310
  if (v === 0) return [0, 0];
2564
2311
  const u = (x - c) / half;
2565
2312
  const widen = v > 0 ? 0.05 : 0.03;
@@ -2569,7 +2316,7 @@ function mouthForm(mesh, box) {
2569
2316
  function mouthOpenGrow(mesh, box) {
2570
2317
  const c = cx(box);
2571
2318
  return localWarp(
2572
- P2.MouthOpen,
2319
+ P.MouthOpen,
2573
2320
  mesh,
2574
2321
  box,
2575
2322
  [0, 1],
@@ -2578,7 +2325,7 @@ function mouthOpenGrow(mesh, box) {
2578
2325
  }
2579
2326
  function mouthWiden(mesh, box) {
2580
2327
  const c = cx(box);
2581
- return localWarp(P2.MouthOpen, mesh, box, [0, 1], ([x], v) => [
2328
+ return localWarp(P.MouthOpen, mesh, box, [0, 1], ([x], v) => [
2582
2329
  v * (AMPLITUDE.mouthOpenWidth - 1) * (x - c),
2583
2330
  0
2584
2331
  ]);
@@ -2596,7 +2343,7 @@ function hairSway(param, mesh, box, amp) {
2596
2343
  );
2597
2344
  }
2598
2345
  function tiltHang(mesh, box, pivot, weight, deg = ROLL_DEG) {
2599
- return localWarp(P2.AngleZ, mesh, box, Z_STOPS, ([x, y], v, i) => {
2346
+ return localWarp(P.AngleZ, mesh, box, Z_STOPS, ([x, y], v, i) => {
2600
2347
  const g = weight(y, i);
2601
2348
  if (g === 0 || v === 0) return [0, 0];
2602
2349
  const a = -g * rollOf(v, deg);
@@ -2845,16 +2592,16 @@ function buildBodyWarp({
2845
2592
  points: ys.flatMap((y) => xs.flatMap((x) => [x, y]))
2846
2593
  },
2847
2594
  warps: [
2848
- warp(P3.BodyAngleX, BODY_STOPS, (x, _y, t) => [turn(x, t), 0]),
2849
- warp(P3.BodyAngleY, BODY_STOPS, (_x, _y, t) => [0, t * BODY.bow * hh]),
2595
+ warp(P2.BodyAngleX, BODY_STOPS, (x, _y, t) => [turn(x, t), 0]),
2596
+ warp(P2.BodyAngleY, BODY_STOPS, (_x, _y, t) => [0, t * BODY.bow * hh]),
2850
2597
  warp(
2851
- P3.BodyAngleZ,
2598
+ P2.BodyAngleZ,
2852
2599
  BODY_STOPS,
2853
2600
  (x, y, t) => roll(x, y, -t * BODY.rollDeg)
2854
2601
  ),
2855
- warp(P3.AngleX, X_STOPS, (x, _y, t) => [BODY.followX * turn(x, t), 0]),
2856
- warp(P3.AngleZ, Z_STOPS, (x, y, t) => roll(x, y, -t * BODY.followRoll)),
2857
- warp(P3.Breath, [0, 1], (_x, _y, t) => [0, t * AMPLITUDE.breathBody * hh])
2602
+ warp(P2.AngleX, X_STOPS, (x, _y, t) => [BODY.followX * turn(x, t), 0]),
2603
+ warp(P2.AngleZ, Z_STOPS, (x, y, t) => roll(x, y, -t * BODY.followRoll)),
2604
+ warp(P2.Breath, [0, 1], (_x, _y, t) => [0, t * AMPLITUDE.breathBody * hh])
2858
2605
  ]
2859
2606
  };
2860
2607
  refuseFold(body, deformer, anchor, [chin, ...pivots]);
@@ -2937,19 +2684,25 @@ function headOwnBreath(hasBody) {
2937
2684
  var ELBOW_AT = 0.42;
2938
2685
  var CAP_ROWS = [0.1, 0.3];
2939
2686
  var CAP_SEGMENTS = 12;
2940
- var ARM_RANGE = [-30, 150];
2687
+ var CAP_INSET = 3;
2688
+ var capInset = (i) => Math.round(CAP_INSET * Math.min(1, Math.min(i, CAP_SEGMENTS - i) / 2));
2689
+ var CAP_RIM = 0.25;
2690
+ var ARM_RANGE = [-8, 32];
2691
+ var ELBOW_RANGE = [-10, 90];
2941
2692
  var ARM_PARAMS = {
2942
- arm_L: { arm: P4.ArmLeft, elbow: P4.ElbowLeft },
2943
- arm_R: { arm: P4.ArmRight, elbow: P4.ElbowRight }
2693
+ arm_L: { arm: P3.ArmLeft, elbow: P3.ElbowLeft },
2694
+ arm_R: { arm: P3.ArmRight, elbow: P3.ElbowRight }
2944
2695
  };
2945
2696
  var ARM_IDS = {
2946
2697
  arm_L: {
2947
2698
  forearm: "forearm_L",
2699
+ cap: "elbow_L",
2948
2700
  armDeformer: "armDeformer_L",
2949
2701
  forearmDeformer: "forearmDeformer_L"
2950
2702
  },
2951
2703
  arm_R: {
2952
2704
  forearm: "forearm_R",
2705
+ cap: "elbow_R",
2953
2706
  armDeformer: "armDeformer_R",
2954
2707
  forearmDeformer: "forearmDeformer_R"
2955
2708
  }
@@ -3002,11 +2755,30 @@ function armGeometry(layer, bodyAxisX) {
3002
2755
  b.x1 - elbow.x,
3003
2756
  b.y1 - elbow.y
3004
2757
  );
2758
+ const inside = (x, y) => {
2759
+ const k = Math.floor(y) - bbox.y;
2760
+ if (k < 0 || k >= bbox.h) return false;
2761
+ const [rs, re] = span(k);
2762
+ return rs < x && x < re;
2763
+ };
2764
+ const capRays = Array.from({ length: CAP_SEGMENTS + 1 }, (_, i) => {
2765
+ const a = Math.PI * i / CAP_SEGMENTS;
2766
+ let t = 1;
2767
+ while (t <= capRadius + CAP_INSET && inside(
2768
+ elbow.x + canvasW / 2 + t * Math.cos(a),
2769
+ canvasH / 2 - elbow.y - t * Math.sin(a)
2770
+ )) {
2771
+ t++;
2772
+ }
2773
+ return Math.min(capRadius, Math.max(1, t - capInset(i)));
2774
+ });
3005
2775
  return {
3006
2776
  shoulder,
3007
2777
  shoulderRadius: ru,
3008
2778
  elbow,
3009
2779
  capRadius,
2780
+ capRays,
2781
+ seamRun: [s - bbox.x, e - bbox.x],
3010
2782
  side: shoulder.x > bodyAxisX ? 1 : -1
3011
2783
  };
3012
2784
  }
@@ -3021,73 +2793,358 @@ function armParts(role, layer, g, order) {
3021
2793
  const lx = (x) => roundTo((x - cx(b)) / bw(b), 1e-5);
3022
2794
  const ly = (y) => roundTo((y - cy(b)) / bh(b), 1e-5);
3023
2795
  const seam = ly(g.elbow.y);
3024
- const band = (top, bottom) => [
2796
+ const band = (top2, bottom) => [
3025
2797
  -0.5,
3026
- top,
2798
+ top2,
3027
2799
  0.5,
3028
- top,
2800
+ top2,
3029
2801
  -0.5,
3030
2802
  bottom,
3031
2803
  0.5,
3032
2804
  bottom
3033
2805
  ];
3034
2806
  const quad = [2, 3, 0, 0, 3, 1];
3035
- const cap = [lx(g.elbow.x), seam];
2807
+ const rays = g.capRays.length;
2808
+ const top = (rays - 1) / 2;
2809
+ const at = (i, r) => {
2810
+ const a = Math.PI * i / (rays - 1);
2811
+ return [lx(g.elbow.x + r * Math.cos(a)), ly(g.elbow.y + r * Math.sin(a))];
2812
+ };
2813
+ const width = (i) => Math.min(CAP_RIM * g.capRadius, g.capRays[i] / 2);
2814
+ const ringRays = Array.from({ length: rays }, (_, i) => i);
2815
+ ringRays.splice(top, 0, top);
2816
+ const vertices = [lx(g.elbow.x), seam];
2817
+ for (let i = 0; i < rays; i++) {
2818
+ vertices.push(...at(i, g.capRays[i] - width(i)));
2819
+ }
2820
+ const ringInner = vertices.length / 2;
2821
+ for (const i of ringRays) vertices.push(...at(i, g.capRays[i] - width(i)));
2822
+ const ringOuter = vertices.length / 2;
2823
+ for (const i of ringRays) vertices.push(...at(i, g.capRays[i]));
2824
+ const { uvs } = meshOf(vertices, []);
2825
+ const col = (c) => roundTo(Math.min(Math.max(c + 0.5, 0.5), bw(b) - 0.5) / bw(b), 1e-5);
2826
+ const v = roundTo(0.5 - seam, 1e-5);
2827
+ ringRays.forEach((i, j) => {
2828
+ const right = j <= top;
2829
+ const outer = right ? g.seamRun[1] : g.seamRun[0] - 1;
2830
+ const step = right ? -width(i) : width(i);
2831
+ uvs[(ringInner + j) * 2] = col(outer + step);
2832
+ uvs[(ringInner + j) * 2 + 1] = v;
2833
+ uvs[(ringOuter + j) * 2] = col(outer);
2834
+ uvs[(ringOuter + j) * 2 + 1] = v;
2835
+ });
3036
2836
  const fan = [];
3037
- for (let i = 0; i <= CAP_SEGMENTS; i++) {
3038
- const a = Math.PI * i / CAP_SEGMENTS;
3039
- cap.push(
3040
- lx(g.elbow.x + g.capRadius * Math.cos(a)),
3041
- ly(g.elbow.y + g.capRadius * Math.sin(a))
3042
- );
3043
- if (i > 0) fan.push(4, 4 + i, 5 + i);
3044
- }
3045
- const part = (id, deformer, at, mesh) => ({
2837
+ for (let i = 0; i < rays - 1; i++) fan.push(0, 1 + i, 2 + i);
2838
+ const ring = [];
2839
+ for (let j = 0; j < ringRays.length - 1; j++) {
2840
+ if (j === top) continue;
2841
+ const [p, q] = [ringInner + j, ringOuter + j];
2842
+ ring.push(p, q, q + 1, p, q + 1, p + 1);
2843
+ }
2844
+ const part = (id, deformer, z, mesh) => ({
3046
2845
  id,
3047
2846
  color: [1, 1, 1, 1],
3048
2847
  width: bw(b),
3049
2848
  height: bh(b),
3050
2849
  transform: { x: cx(b), y: cy(b) },
3051
- order: at,
2850
+ order: z,
3052
2851
  deformer,
3053
2852
  mesh
3054
2853
  });
3055
2854
  const ids = ARM_IDS[role];
3056
2855
  return [
3057
- part(role, ids.armDeformer, order, meshOf(band(0.5, seam), quad)),
2856
+ part(ids.cap, ids.forearmDeformer, order, {
2857
+ vertices,
2858
+ uvs,
2859
+ indices: [...fan, ...ring]
2860
+ }),
2861
+ part(role, ids.armDeformer, order + 1, meshOf(band(0.5, seam), quad)),
3058
2862
  part(
3059
2863
  ids.forearm,
3060
2864
  ids.forearmDeformer,
3061
- order + 1,
3062
- meshOf([...band(seam, -0.5), ...cap], [...quad, ...fan])
2865
+ order + 2,
2866
+ meshOf(band(seam, -0.5), quad)
3063
2867
  )
3064
2868
  ];
3065
2869
  }
3066
2870
  function armDeformers(role, g) {
3067
2871
  const ids = ARM_IDS[role];
3068
2872
  const params = ARM_PARAMS[role];
3069
- const rotate = (parameter) => ({
2873
+ const rotate = (parameter, range, sign) => ({
3070
2874
  parameter,
3071
2875
  channel: "rotate",
3072
- from: g.side * ARM_RANGE[0],
3073
- to: g.side * ARM_RANGE[1]
2876
+ from: sign * range[0],
2877
+ to: sign * range[1]
3074
2878
  });
3075
2879
  return [
3076
2880
  {
3077
2881
  id: ids.armDeformer,
3078
2882
  parent: BODY_WARP_ID,
3079
2883
  pivot: { ...g.shoulder },
3080
- bindings: [rotate(params.arm)]
2884
+ bindings: [rotate(params.arm, ARM_RANGE, g.side)]
3081
2885
  },
3082
2886
  {
3083
2887
  id: ids.forearmDeformer,
3084
2888
  parent: ids.armDeformer,
3085
2889
  pivot: { ...g.elbow },
3086
- bindings: [rotate(params.elbow)]
2890
+ bindings: [rotate(params.elbow, ELBOW_RANGE, -g.side)]
3087
2891
  }
3088
2892
  ];
3089
2893
  }
3090
2894
 
2895
+ // src/auto-rig/roles.ts
2896
+ var ROLE_TABLE = [
2897
+ { role: "hair_back", family: "hair_back" },
2898
+ { role: "body", family: "body" },
2899
+ { role: "arm_L", family: "arm", parts: ["elbow_L", "arm_L", "forearm_L"] },
2900
+ { role: "arm_R", family: "arm", parts: ["elbow_R", "arm_R", "forearm_R"] },
2901
+ { role: "face", family: "face", required: true },
2902
+ { role: "blush_L", family: "face" },
2903
+ { role: "blush_R", family: "face" },
2904
+ { role: "nose", family: "nose" },
2905
+ { role: "mouth", family: "mouth", required: true },
2906
+ { role: "mouth_open", family: "mouth" },
2907
+ { role: "eye_L", family: "eye_L", required: true },
2908
+ { role: "eye_R", family: "eye_R", required: true },
2909
+ { role: "iris_L", family: "eye_L" },
2910
+ { role: "iris_R", family: "eye_R" },
2911
+ { role: "pupil_L", family: "eye_L" },
2912
+ { role: "pupil_R", family: "eye_R" },
2913
+ { role: "highlight_L", family: "eye_L" },
2914
+ { role: "highlight_R", family: "eye_R" },
2915
+ { role: "lash_lower_L", family: "eye_L" },
2916
+ { role: "lash_lower_R", family: "eye_R" },
2917
+ { role: "lash_L", family: "eye_L" },
2918
+ { role: "lash_R", family: "eye_R" },
2919
+ { role: "brow_L", family: "brow_L" },
2920
+ { role: "brow_R", family: "brow_R" },
2921
+ { role: "hair_front", family: "hair_front" }
2922
+ ];
2923
+ var REQUIRED_ROLES = ROLE_TABLE.filter(
2924
+ (r) => r.required
2925
+ ).map((r) => r.role);
2926
+ var SPEC_BY_ROLE = new Map(ROLE_TABLE.map((r, i) => [r.role, { ...r, i }]));
2927
+ function roleSpec(role) {
2928
+ const spec = SPEC_BY_ROLE.get(role);
2929
+ if (spec === void 0) throw new Error(`auto-rig: unknown role "${role}"`);
2930
+ return spec;
2931
+ }
2932
+ function partIdsOfRole(role) {
2933
+ return roleSpec(role).parts ?? [role];
2934
+ }
2935
+ var ROLE_BY_PART = new Map(
2936
+ ROLE_TABLE.flatMap((r) => (r.parts ?? []).map((p) => [p, r.role]))
2937
+ );
2938
+ function roleOfPart(partId) {
2939
+ return ROLE_BY_PART.get(partId) ?? partId;
2940
+ }
2941
+ var ALIASES = {
2942
+ eyebrow_L: "brow_L",
2943
+ eyebrow_R: "brow_R",
2944
+ eye_white_L: "eye_L",
2945
+ eye_white_R: "eye_R"
2946
+ };
2947
+ function roleOf(fileName) {
2948
+ const role = fileName.replace(/\.[^.]+$/, "").toLowerCase().replace(/[-\s]+/g, "_").replace(/_([lr])$/, (_, s) => `_${s.toUpperCase()}`);
2949
+ return ALIASES[role] ?? role;
2950
+ }
2951
+ function checkArmsHaveBody(roles) {
2952
+ const has = new Set(roles);
2953
+ if (has.has("body")) return;
2954
+ const arm = ROLE_TABLE.find((r) => r.family === "arm" && has.has(r.role));
2955
+ if (arm !== void 0) {
2956
+ throw new Error(
2957
+ `auto-rig: ${arm.role} needs a body layer (an arm hangs from the body's shoulder)`
2958
+ );
2959
+ }
2960
+ }
2961
+ function parseLayerRoles(fileNames) {
2962
+ const seen = /* @__PURE__ */ new Map();
2963
+ const out = [];
2964
+ for (const fileName of fileNames) {
2965
+ const role = roleOf(fileName);
2966
+ if (!SPEC_BY_ROLE.has(role)) {
2967
+ throw new Error(
2968
+ `auto-rig: "${fileName}" names no known role (expected one of ${ROLE_TABLE.map((r) => r.role).join(", ")})`
2969
+ );
2970
+ }
2971
+ const prior = seen.get(role);
2972
+ if (prior !== void 0) {
2973
+ throw new Error(
2974
+ `auto-rig: role "${role}" is named twice ("${prior}" and "${fileName}")`
2975
+ );
2976
+ }
2977
+ seen.set(role, fileName);
2978
+ out.push({ role, fileName });
2979
+ }
2980
+ const missing = REQUIRED_ROLES.filter((r) => !seen.has(r));
2981
+ if (missing.length > 0) {
2982
+ throw new Error(
2983
+ `auto-rig: missing required role${missing.length > 1 ? "s" : ""} ${missing.join(", ")}`
2984
+ );
2985
+ }
2986
+ checkArmsHaveBody(seen.keys());
2987
+ return out;
2988
+ }
2989
+
2990
+ // src/auto-rig/generate.ts
2991
+ import {
2992
+ IKI_FORMAT_VERSION as IKI_FORMAT_VERSION2,
2993
+ StandardParameter as P6,
2994
+ parseIkiModel as parseIkiModel3
2995
+ } from "@ikijs/format";
2996
+
2997
+ // src/auto-rig/animations.ts
2998
+ import {
2999
+ StandardParameter as P4
3000
+ } from "@ikijs/format";
3001
+ function browsY(y) {
3002
+ return [
3003
+ { parameter: P4.BrowLeftY, value: y, blend: "add" },
3004
+ { parameter: P4.BrowRightY, value: y, blend: "add" }
3005
+ ];
3006
+ }
3007
+ function browsTilt(innerDown) {
3008
+ return [
3009
+ { parameter: P4.BrowLeftAngle, value: innerDown, blend: "add" },
3010
+ { parameter: P4.BrowRightAngle, value: -innerDown, blend: "add" }
3011
+ ];
3012
+ }
3013
+ function cheek(value) {
3014
+ return { parameter: P4.Cheek, value, blend: "add" };
3015
+ }
3016
+ var BROWS = [P4.BrowLeftY, P4.BrowRightY, P4.BrowLeftAngle, P4.BrowRightAngle];
3017
+ var EXPRESSIONS = [
3018
+ {
3019
+ id: "smile",
3020
+ description: "Smiling and pleased: warm, friendly, content. For greetings, thanks, agreement and gentle happiness.",
3021
+ parameters: [
3022
+ { parameter: P4.MouthForm, value: 1, blend: "add" },
3023
+ ...browsY(0.25),
3024
+ cheek(0.4)
3025
+ ]
3026
+ },
3027
+ {
3028
+ id: "laugh",
3029
+ description: "Laughing, eyes shut and mouth open: delighted, amused. For jokes, playfulness and big joy.",
3030
+ parameters: [
3031
+ { parameter: P4.EyeOpenLeft, value: 0, blend: "multiply" },
3032
+ { parameter: P4.EyeOpenRight, value: 0, blend: "multiply" },
3033
+ { parameter: P4.MouthForm, value: 1, blend: "add" },
3034
+ { parameter: P4.MouthOpen, value: 0.8, blend: "overwrite" },
3035
+ ...browsY(0.4),
3036
+ cheek(0.65)
3037
+ ]
3038
+ },
3039
+ {
3040
+ id: "angry",
3041
+ description: "Angry or annoyed, frowning. For irritation, frustration, indignation or scolding.",
3042
+ needs: BROWS,
3043
+ parameters: [
3044
+ ...browsY(-0.5),
3045
+ ...browsTilt(0.8),
3046
+ { parameter: P4.MouthForm, value: -0.9, blend: "add" }
3047
+ ]
3048
+ },
3049
+ {
3050
+ id: "sad",
3051
+ description: "Sad or disappointed, downcast. For sorrow, regret, apology or sympathy.",
3052
+ needs: BROWS,
3053
+ parameters: [
3054
+ ...browsY(0.35),
3055
+ ...browsTilt(-0.8),
3056
+ { parameter: P4.MouthForm, value: -0.8, blend: "add" }
3057
+ ]
3058
+ },
3059
+ {
3060
+ id: "surprised",
3061
+ description: "Surprised, mouth dropped open: startled, amazed. For shock, sudden news or disbelief.",
3062
+ parameters: [
3063
+ ...browsY(0.8),
3064
+ { parameter: P4.MouthOpen, value: 0.9, blend: "overwrite" }
3065
+ ]
3066
+ },
3067
+ {
3068
+ id: "shy",
3069
+ description: "Shy or embarrassed: bashful, flustered. For being praised, teased or caught off guard.",
3070
+ needs: [P4.Cheek],
3071
+ parameters: [
3072
+ cheek(1),
3073
+ // Gaze down.
3074
+ { parameter: P4.EyeballY, value: -0.65, blend: "add" },
3075
+ { parameter: P4.MouthForm, value: 0.4, blend: "add" },
3076
+ ...browsTilt(-0.25)
3077
+ ]
3078
+ }
3079
+ ];
3080
+ function defaultExpressions(declared) {
3081
+ return EXPRESSIONS.filter(
3082
+ (e) => e.needs === void 0 || e.needs.some((id) => declared.has(id))
3083
+ ).map(({ needs: _needs, ...e }) => ({
3084
+ ...e,
3085
+ parameters: e.parameters.filter((t) => declared.has(t.parameter))
3086
+ }));
3087
+ }
3088
+ var DEFAULT_MOTIONS = {
3089
+ Nod: [
3090
+ {
3091
+ description: "Nods yes: agreement, acknowledgement, understanding.",
3092
+ duration: 1,
3093
+ curves: [
3094
+ {
3095
+ parameter: P4.AngleY,
3096
+ keys: [
3097
+ [0, 0],
3098
+ [0.3, -18],
3099
+ [0.6, 4],
3100
+ [0.8, -1],
3101
+ [1, 0]
3102
+ ]
3103
+ }
3104
+ ]
3105
+ }
3106
+ ],
3107
+ Shake: [
3108
+ {
3109
+ description: "Shakes the head no: disagreement, refusal, disbelief.",
3110
+ duration: 1.2,
3111
+ // The first swing peaks at 0.2 s, which the default fade would still
3112
+ // be damping.
3113
+ fadeIn: 0.15,
3114
+ curves: [
3115
+ {
3116
+ parameter: P4.AngleX,
3117
+ keys: [
3118
+ [0, 0],
3119
+ [0.2, -18],
3120
+ [0.45, 18],
3121
+ [0.7, -16],
3122
+ [0.95, 10],
3123
+ [1.2, 0]
3124
+ ]
3125
+ }
3126
+ ]
3127
+ }
3128
+ ],
3129
+ Tilt: [
3130
+ {
3131
+ description: "Tilts the head to one side and back: curiosity, puzzlement, a question.",
3132
+ duration: 1.4,
3133
+ curves: [
3134
+ {
3135
+ parameter: P4.AngleZ,
3136
+ keys: [
3137
+ [0, 0],
3138
+ [0.35, 20],
3139
+ [1, 20],
3140
+ [1.4, 0]
3141
+ ]
3142
+ }
3143
+ ]
3144
+ }
3145
+ ]
3146
+ };
3147
+
3091
3148
  // src/auto-rig/checks.ts
3092
3149
  var finite = (v) => typeof v === "number" && Number.isFinite(v);
3093
3150
  var isObject = (v) => typeof v === "object" && v !== null;
@@ -5659,13 +5716,14 @@ function declareParameters(roles) {
5659
5716
  add(P6.BodyAngleZ, "Body Angle Z", -10, 10, 0);
5660
5717
  }
5661
5718
  const [armMin, armMax] = ARM_RANGE;
5719
+ const [elbowMin, elbowMax] = ELBOW_RANGE;
5662
5720
  if (roles.has("arm_L")) {
5663
5721
  add(P6.ArmLeft, "Arm L", armMin, armMax, 0);
5664
- add(P6.ElbowLeft, "Elbow L", armMin, armMax, 0);
5722
+ add(P6.ElbowLeft, "Elbow L", elbowMin, elbowMax, 0);
5665
5723
  }
5666
5724
  if (roles.has("arm_R")) {
5667
5725
  add(P6.ArmRight, "Arm R", armMin, armMax, 0);
5668
- add(P6.ElbowRight, "Elbow R", armMin, armMax, 0);
5726
+ add(P6.ElbowRight, "Elbow R", elbowMin, elbowMax, 0);
5669
5727
  }
5670
5728
  add(P6.EyeOpenLeft, "Eye L", 0, 1, 1);
5671
5729
  add(P6.EyeOpenRight, "Eye R", 0, 1, 1);
@@ -5726,6 +5784,7 @@ export {
5726
5784
  SetPhysicsRig,
5727
5785
  TurnTargetError,
5728
5786
  UV_INSET_PX,
5787
+ armGeometry,
5729
5788
  captureBindingEndpoint,
5730
5789
  computeGridOffsets,
5731
5790
  createDefaultMatrixDeformer,