@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.js CHANGED
@@ -52,6 +52,7 @@ __export(index_exports, {
52
52
  SetPhysicsRig: () => SetPhysicsRig,
53
53
  TurnTargetError: () => TurnTargetError,
54
54
  UV_INSET_PX: () => UV_INSET_PX,
55
+ armGeometry: () => armGeometry,
55
56
  captureBindingEndpoint: () => captureBindingEndpoint,
56
57
  computeGridOffsets: () => computeGridOffsets,
57
58
  createDefaultMatrixDeformer: () => createDefaultMatrixDeformer,
@@ -2180,258 +2181,11 @@ var DEFAULT_TURN_TARGETS = Object.freeze({
2180
2181
  silhouetteRatio: 1
2181
2182
  });
2182
2183
 
2183
- // src/auto-rig/roles.ts
2184
- var ROLE_TABLE = [
2185
- { role: "hair_back", family: "hair_back" },
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"] },
2189
- { role: "face", family: "face", required: true },
2190
- { role: "blush_L", family: "face" },
2191
- { role: "blush_R", family: "face" },
2192
- { role: "nose", family: "nose" },
2193
- { role: "mouth", family: "mouth", required: true },
2194
- { role: "mouth_open", family: "mouth" },
2195
- { role: "eye_L", family: "eye_L", required: true },
2196
- { role: "eye_R", family: "eye_R", required: true },
2197
- { role: "iris_L", family: "eye_L" },
2198
- { role: "iris_R", family: "eye_R" },
2199
- { role: "pupil_L", family: "eye_L" },
2200
- { role: "pupil_R", family: "eye_R" },
2201
- { role: "highlight_L", family: "eye_L" },
2202
- { role: "highlight_R", family: "eye_R" },
2203
- { role: "lash_lower_L", family: "eye_L" },
2204
- { role: "lash_lower_R", family: "eye_R" },
2205
- { role: "lash_L", family: "eye_L" },
2206
- { role: "lash_R", family: "eye_R" },
2207
- { role: "brow_L", family: "brow_L" },
2208
- { role: "brow_R", family: "brow_R" },
2209
- { role: "hair_front", family: "hair_front" }
2210
- ];
2211
- var REQUIRED_ROLES = ROLE_TABLE.filter(
2212
- (r) => r.required
2213
- ).map((r) => r.role);
2214
- var SPEC_BY_ROLE = new Map(ROLE_TABLE.map((r, i) => [r.role, { ...r, i }]));
2215
- function roleSpec(role) {
2216
- const spec = SPEC_BY_ROLE.get(role);
2217
- if (spec === void 0) throw new Error(`auto-rig: unknown role "${role}"`);
2218
- return spec;
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
- }
2229
- var ALIASES = {
2230
- eyebrow_L: "brow_L",
2231
- eyebrow_R: "brow_R",
2232
- eye_white_L: "eye_L",
2233
- eye_white_R: "eye_R"
2234
- };
2235
- function roleOf(fileName) {
2236
- const role = fileName.replace(/\.[^.]+$/, "").toLowerCase().replace(/[-\s]+/g, "_").replace(/_([lr])$/, (_, s) => `_${s.toUpperCase()}`);
2237
- return ALIASES[role] ?? role;
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
- }
2249
- function parseLayerRoles(fileNames) {
2250
- const seen = /* @__PURE__ */ new Map();
2251
- const out = [];
2252
- for (const fileName of fileNames) {
2253
- const role = roleOf(fileName);
2254
- if (!SPEC_BY_ROLE.has(role)) {
2255
- throw new Error(
2256
- `auto-rig: "${fileName}" names no known role (expected one of ${ROLE_TABLE.map((r) => r.role).join(", ")})`
2257
- );
2258
- }
2259
- const prior = seen.get(role);
2260
- if (prior !== void 0) {
2261
- throw new Error(
2262
- `auto-rig: role "${role}" is named twice ("${prior}" and "${fileName}")`
2263
- );
2264
- }
2265
- seen.set(role, fileName);
2266
- out.push({ role, fileName });
2267
- }
2268
- const missing = REQUIRED_ROLES.filter((r) => !seen.has(r));
2269
- if (missing.length > 0) {
2270
- throw new Error(
2271
- `auto-rig: missing required role${missing.length > 1 ? "s" : ""} ${missing.join(", ")}`
2272
- );
2273
- }
2274
- checkArmsHaveBody(seen.keys());
2275
- return out;
2276
- }
2277
-
2278
- // src/auto-rig/generate.ts
2279
- var import_format8 = require("@ikijs/format");
2280
-
2281
- // src/auto-rig/animations.ts
2282
- var import_format3 = require("@ikijs/format");
2283
- function browsY(y) {
2284
- return [
2285
- { parameter: import_format3.StandardParameter.BrowLeftY, value: y, blend: "add" },
2286
- { parameter: import_format3.StandardParameter.BrowRightY, value: y, blend: "add" }
2287
- ];
2288
- }
2289
- function browsTilt(innerDown) {
2290
- return [
2291
- { parameter: import_format3.StandardParameter.BrowLeftAngle, value: innerDown, blend: "add" },
2292
- { parameter: import_format3.StandardParameter.BrowRightAngle, value: -innerDown, blend: "add" }
2293
- ];
2294
- }
2295
- function cheek(value) {
2296
- return { parameter: import_format3.StandardParameter.Cheek, value, blend: "add" };
2297
- }
2298
- var BROWS = [import_format3.StandardParameter.BrowLeftY, import_format3.StandardParameter.BrowRightY, import_format3.StandardParameter.BrowLeftAngle, import_format3.StandardParameter.BrowRightAngle];
2299
- var EXPRESSIONS = [
2300
- {
2301
- id: "smile",
2302
- description: "Smiling and pleased: warm, friendly, content. For greetings, thanks, agreement and gentle happiness.",
2303
- parameters: [
2304
- { parameter: import_format3.StandardParameter.MouthForm, value: 1, blend: "add" },
2305
- ...browsY(0.25),
2306
- cheek(0.4)
2307
- ]
2308
- },
2309
- {
2310
- id: "laugh",
2311
- description: "Laughing, eyes shut and mouth open: delighted, amused. For jokes, playfulness and big joy.",
2312
- parameters: [
2313
- { parameter: import_format3.StandardParameter.EyeOpenLeft, value: 0, blend: "multiply" },
2314
- { parameter: import_format3.StandardParameter.EyeOpenRight, value: 0, blend: "multiply" },
2315
- { parameter: import_format3.StandardParameter.MouthForm, value: 1, blend: "add" },
2316
- { parameter: import_format3.StandardParameter.MouthOpen, value: 0.8, blend: "overwrite" },
2317
- ...browsY(0.4),
2318
- cheek(0.65)
2319
- ]
2320
- },
2321
- {
2322
- id: "angry",
2323
- description: "Angry or annoyed, frowning. For irritation, frustration, indignation or scolding.",
2324
- needs: BROWS,
2325
- parameters: [
2326
- ...browsY(-0.5),
2327
- ...browsTilt(0.8),
2328
- { parameter: import_format3.StandardParameter.MouthForm, value: -0.9, blend: "add" }
2329
- ]
2330
- },
2331
- {
2332
- id: "sad",
2333
- description: "Sad or disappointed, downcast. For sorrow, regret, apology or sympathy.",
2334
- needs: BROWS,
2335
- parameters: [
2336
- ...browsY(0.35),
2337
- ...browsTilt(-0.8),
2338
- { parameter: import_format3.StandardParameter.MouthForm, value: -0.8, blend: "add" }
2339
- ]
2340
- },
2341
- {
2342
- id: "surprised",
2343
- description: "Surprised, mouth dropped open: startled, amazed. For shock, sudden news or disbelief.",
2344
- parameters: [
2345
- ...browsY(0.8),
2346
- { parameter: import_format3.StandardParameter.MouthOpen, value: 0.9, blend: "overwrite" }
2347
- ]
2348
- },
2349
- {
2350
- id: "shy",
2351
- description: "Shy or embarrassed: bashful, flustered. For being praised, teased or caught off guard.",
2352
- needs: [import_format3.StandardParameter.Cheek],
2353
- parameters: [
2354
- cheek(1),
2355
- // Gaze down.
2356
- { parameter: import_format3.StandardParameter.EyeballY, value: -0.65, blend: "add" },
2357
- { parameter: import_format3.StandardParameter.MouthForm, value: 0.4, blend: "add" },
2358
- ...browsTilt(-0.25)
2359
- ]
2360
- }
2361
- ];
2362
- function defaultExpressions(declared) {
2363
- return EXPRESSIONS.filter(
2364
- (e) => e.needs === void 0 || e.needs.some((id) => declared.has(id))
2365
- ).map(({ needs: _needs, ...e }) => ({
2366
- ...e,
2367
- parameters: e.parameters.filter((t) => declared.has(t.parameter))
2368
- }));
2369
- }
2370
- var DEFAULT_MOTIONS = {
2371
- Nod: [
2372
- {
2373
- description: "Nods yes: agreement, acknowledgement, understanding.",
2374
- duration: 1,
2375
- curves: [
2376
- {
2377
- parameter: import_format3.StandardParameter.AngleY,
2378
- keys: [
2379
- [0, 0],
2380
- [0.3, -18],
2381
- [0.6, 4],
2382
- [0.8, -1],
2383
- [1, 0]
2384
- ]
2385
- }
2386
- ]
2387
- }
2388
- ],
2389
- Shake: [
2390
- {
2391
- description: "Shakes the head no: disagreement, refusal, disbelief.",
2392
- duration: 1.2,
2393
- // The first swing peaks at 0.2 s, which the default fade would still
2394
- // be damping.
2395
- fadeIn: 0.15,
2396
- curves: [
2397
- {
2398
- parameter: import_format3.StandardParameter.AngleX,
2399
- keys: [
2400
- [0, 0],
2401
- [0.2, -18],
2402
- [0.45, 18],
2403
- [0.7, -16],
2404
- [0.95, 10],
2405
- [1.2, 0]
2406
- ]
2407
- }
2408
- ]
2409
- }
2410
- ],
2411
- Tilt: [
2412
- {
2413
- description: "Tilts the head to one side and back: curiosity, puzzlement, a question.",
2414
- duration: 1.4,
2415
- curves: [
2416
- {
2417
- parameter: import_format3.StandardParameter.AngleZ,
2418
- keys: [
2419
- [0, 0],
2420
- [0.35, 20],
2421
- [1, 20],
2422
- [1.4, 0]
2423
- ]
2424
- }
2425
- ]
2426
- }
2427
- ]
2428
- };
2429
-
2430
2184
  // src/auto-rig/arms.ts
2431
- var import_format6 = require("@ikijs/format");
2185
+ var import_format5 = require("@ikijs/format");
2432
2186
 
2433
2187
  // src/auto-rig/body.ts
2434
- var import_format5 = require("@ikijs/format");
2188
+ var import_format4 = require("@ikijs/format");
2435
2189
 
2436
2190
  // src/auto-rig/layout.ts
2437
2191
  function boxOfLayer(layer) {
@@ -2507,7 +2261,7 @@ function meshPoints(mesh, box) {
2507
2261
  }
2508
2262
 
2509
2263
  // src/auto-rig/drivers.ts
2510
- var import_format4 = require("@ikijs/format");
2264
+ var import_format3 = require("@ikijs/format");
2511
2265
  var SWAY_RANGE = 20;
2512
2266
  var SWAY_CURVE = 1.4;
2513
2267
  var HAIR_HANG = 0.35;
@@ -2585,13 +2339,13 @@ function gazeBindings(irisW, share = 1) {
2585
2339
  const gy = AMPLITUDE.gazeY * irisW * share;
2586
2340
  return [
2587
2341
  {
2588
- parameter: import_format4.StandardParameter.EyeballX,
2342
+ parameter: import_format3.StandardParameter.EyeballX,
2589
2343
  channel: "translateX",
2590
2344
  from: -round1(gx),
2591
2345
  to: round1(gx)
2592
2346
  },
2593
2347
  {
2594
- parameter: import_format4.StandardParameter.EyeballY,
2348
+ parameter: import_format3.StandardParameter.EyeballY,
2595
2349
  channel: "translateY",
2596
2350
  from: -round1(gy),
2597
2351
  to: round1(gy)
@@ -2605,8 +2359,8 @@ function browReach(hh) {
2605
2359
  return AMPLITUDE.brow * hh;
2606
2360
  }
2607
2361
  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;
2362
+ const y = side === "L" ? import_format3.StandardParameter.BrowLeftY : import_format3.StandardParameter.BrowRightY;
2363
+ const a = side === "L" ? import_format3.StandardParameter.BrowLeftAngle : import_format3.StandardParameter.BrowRightAngle;
2610
2364
  return [
2611
2365
  {
2612
2366
  parameter: y,
@@ -2622,7 +2376,7 @@ function mouthForm(mesh, box) {
2622
2376
  const a = 0.3 * bh(box);
2623
2377
  const c = cx(box);
2624
2378
  const half = bw(box) / 2;
2625
- return localWarp(import_format4.StandardParameter.MouthForm, mesh, box, [-1, 0, 1], ([x], v) => {
2379
+ return localWarp(import_format3.StandardParameter.MouthForm, mesh, box, [-1, 0, 1], ([x], v) => {
2626
2380
  if (v === 0) return [0, 0];
2627
2381
  const u = (x - c) / half;
2628
2382
  const widen = v > 0 ? 0.05 : 0.03;
@@ -2632,7 +2386,7 @@ function mouthForm(mesh, box) {
2632
2386
  function mouthOpenGrow(mesh, box) {
2633
2387
  const c = cx(box);
2634
2388
  return localWarp(
2635
- import_format4.StandardParameter.MouthOpen,
2389
+ import_format3.StandardParameter.MouthOpen,
2636
2390
  mesh,
2637
2391
  box,
2638
2392
  [0, 1],
@@ -2641,7 +2395,7 @@ function mouthOpenGrow(mesh, box) {
2641
2395
  }
2642
2396
  function mouthWiden(mesh, box) {
2643
2397
  const c = cx(box);
2644
- return localWarp(import_format4.StandardParameter.MouthOpen, mesh, box, [0, 1], ([x], v) => [
2398
+ return localWarp(import_format3.StandardParameter.MouthOpen, mesh, box, [0, 1], ([x], v) => [
2645
2399
  v * (AMPLITUDE.mouthOpenWidth - 1) * (x - c),
2646
2400
  0
2647
2401
  ]);
@@ -2659,7 +2413,7 @@ function hairSway(param, mesh, box, amp) {
2659
2413
  );
2660
2414
  }
2661
2415
  function tiltHang(mesh, box, pivot, weight, deg = ROLL_DEG) {
2662
- return localWarp(import_format4.StandardParameter.AngleZ, mesh, box, Z_STOPS, ([x, y], v, i) => {
2416
+ return localWarp(import_format3.StandardParameter.AngleZ, mesh, box, Z_STOPS, ([x, y], v, i) => {
2663
2417
  const g = weight(y, i);
2664
2418
  if (g === 0 || v === 0) return [0, 0];
2665
2419
  const a = -g * rollOf(v, deg);
@@ -2908,16 +2662,16 @@ function buildBodyWarp({
2908
2662
  points: ys.flatMap((y) => xs.flatMap((x) => [x, y]))
2909
2663
  },
2910
2664
  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]),
2665
+ warp(import_format4.StandardParameter.BodyAngleX, BODY_STOPS, (x, _y, t) => [turn(x, t), 0]),
2666
+ warp(import_format4.StandardParameter.BodyAngleY, BODY_STOPS, (_x, _y, t) => [0, t * BODY.bow * hh]),
2913
2667
  warp(
2914
- import_format5.StandardParameter.BodyAngleZ,
2668
+ import_format4.StandardParameter.BodyAngleZ,
2915
2669
  BODY_STOPS,
2916
2670
  (x, y, t) => roll(x, y, -t * BODY.rollDeg)
2917
2671
  ),
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])
2672
+ warp(import_format4.StandardParameter.AngleX, X_STOPS, (x, _y, t) => [BODY.followX * turn(x, t), 0]),
2673
+ warp(import_format4.StandardParameter.AngleZ, Z_STOPS, (x, y, t) => roll(x, y, -t * BODY.followRoll)),
2674
+ warp(import_format4.StandardParameter.Breath, [0, 1], (_x, _y, t) => [0, t * AMPLITUDE.breathBody * hh])
2921
2675
  ]
2922
2676
  };
2923
2677
  refuseFold(body, deformer, anchor, [chin, ...pivots]);
@@ -3000,19 +2754,25 @@ function headOwnBreath(hasBody) {
3000
2754
  var ELBOW_AT = 0.42;
3001
2755
  var CAP_ROWS = [0.1, 0.3];
3002
2756
  var CAP_SEGMENTS = 12;
3003
- var ARM_RANGE = [-30, 150];
2757
+ var CAP_INSET = 3;
2758
+ var capInset = (i) => Math.round(CAP_INSET * Math.min(1, Math.min(i, CAP_SEGMENTS - i) / 2));
2759
+ var CAP_RIM = 0.25;
2760
+ var ARM_RANGE = [-8, 32];
2761
+ var ELBOW_RANGE = [-10, 90];
3004
2762
  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 }
2763
+ arm_L: { arm: import_format5.StandardParameter.ArmLeft, elbow: import_format5.StandardParameter.ElbowLeft },
2764
+ arm_R: { arm: import_format5.StandardParameter.ArmRight, elbow: import_format5.StandardParameter.ElbowRight }
3007
2765
  };
3008
2766
  var ARM_IDS = {
3009
2767
  arm_L: {
3010
2768
  forearm: "forearm_L",
2769
+ cap: "elbow_L",
3011
2770
  armDeformer: "armDeformer_L",
3012
2771
  forearmDeformer: "forearmDeformer_L"
3013
2772
  },
3014
2773
  arm_R: {
3015
2774
  forearm: "forearm_R",
2775
+ cap: "elbow_R",
3016
2776
  armDeformer: "armDeformer_R",
3017
2777
  forearmDeformer: "forearmDeformer_R"
3018
2778
  }
@@ -3065,11 +2825,30 @@ function armGeometry(layer, bodyAxisX) {
3065
2825
  b.x1 - elbow.x,
3066
2826
  b.y1 - elbow.y
3067
2827
  );
2828
+ const inside = (x, y) => {
2829
+ const k = Math.floor(y) - bbox.y;
2830
+ if (k < 0 || k >= bbox.h) return false;
2831
+ const [rs, re] = span(k);
2832
+ return rs < x && x < re;
2833
+ };
2834
+ const capRays = Array.from({ length: CAP_SEGMENTS + 1 }, (_, i) => {
2835
+ const a = Math.PI * i / CAP_SEGMENTS;
2836
+ let t = 1;
2837
+ while (t <= capRadius + CAP_INSET && inside(
2838
+ elbow.x + canvasW / 2 + t * Math.cos(a),
2839
+ canvasH / 2 - elbow.y - t * Math.sin(a)
2840
+ )) {
2841
+ t++;
2842
+ }
2843
+ return Math.min(capRadius, Math.max(1, t - capInset(i)));
2844
+ });
3068
2845
  return {
3069
2846
  shoulder,
3070
2847
  shoulderRadius: ru,
3071
2848
  elbow,
3072
2849
  capRadius,
2850
+ capRays,
2851
+ seamRun: [s - bbox.x, e - bbox.x],
3073
2852
  side: shoulder.x > bodyAxisX ? 1 : -1
3074
2853
  };
3075
2854
  }
@@ -3084,72 +2863,351 @@ function armParts(role, layer, g, order) {
3084
2863
  const lx = (x) => roundTo((x - cx(b)) / bw(b), 1e-5);
3085
2864
  const ly = (y) => roundTo((y - cy(b)) / bh(b), 1e-5);
3086
2865
  const seam = ly(g.elbow.y);
3087
- const band = (top, bottom) => [
2866
+ const band = (top2, bottom) => [
3088
2867
  -0.5,
3089
- top,
2868
+ top2,
3090
2869
  0.5,
3091
- top,
2870
+ top2,
3092
2871
  -0.5,
3093
2872
  bottom,
3094
2873
  0.5,
3095
2874
  bottom
3096
2875
  ];
3097
2876
  const quad = [2, 3, 0, 0, 3, 1];
3098
- const cap = [lx(g.elbow.x), seam];
2877
+ const rays = g.capRays.length;
2878
+ const top = (rays - 1) / 2;
2879
+ const at = (i, r) => {
2880
+ const a = Math.PI * i / (rays - 1);
2881
+ return [lx(g.elbow.x + r * Math.cos(a)), ly(g.elbow.y + r * Math.sin(a))];
2882
+ };
2883
+ const width = (i) => Math.min(CAP_RIM * g.capRadius, g.capRays[i] / 2);
2884
+ const ringRays = Array.from({ length: rays }, (_, i) => i);
2885
+ ringRays.splice(top, 0, top);
2886
+ const vertices = [lx(g.elbow.x), seam];
2887
+ for (let i = 0; i < rays; i++) {
2888
+ vertices.push(...at(i, g.capRays[i] - width(i)));
2889
+ }
2890
+ const ringInner = vertices.length / 2;
2891
+ for (const i of ringRays) vertices.push(...at(i, g.capRays[i] - width(i)));
2892
+ const ringOuter = vertices.length / 2;
2893
+ for (const i of ringRays) vertices.push(...at(i, g.capRays[i]));
2894
+ const { uvs } = meshOf(vertices, []);
2895
+ const col = (c) => roundTo(Math.min(Math.max(c + 0.5, 0.5), bw(b) - 0.5) / bw(b), 1e-5);
2896
+ const v = roundTo(0.5 - seam, 1e-5);
2897
+ ringRays.forEach((i, j) => {
2898
+ const right = j <= top;
2899
+ const outer = right ? g.seamRun[1] : g.seamRun[0] - 1;
2900
+ const step = right ? -width(i) : width(i);
2901
+ uvs[(ringInner + j) * 2] = col(outer + step);
2902
+ uvs[(ringInner + j) * 2 + 1] = v;
2903
+ uvs[(ringOuter + j) * 2] = col(outer);
2904
+ uvs[(ringOuter + j) * 2 + 1] = v;
2905
+ });
3099
2906
  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) => ({
2907
+ for (let i = 0; i < rays - 1; i++) fan.push(0, 1 + i, 2 + i);
2908
+ const ring = [];
2909
+ for (let j = 0; j < ringRays.length - 1; j++) {
2910
+ if (j === top) continue;
2911
+ const [p, q] = [ringInner + j, ringOuter + j];
2912
+ ring.push(p, q, q + 1, p, q + 1, p + 1);
2913
+ }
2914
+ const part = (id, deformer, z, mesh) => ({
3109
2915
  id,
3110
2916
  color: [1, 1, 1, 1],
3111
2917
  width: bw(b),
3112
2918
  height: bh(b),
3113
2919
  transform: { x: cx(b), y: cy(b) },
3114
- order: at,
2920
+ order: z,
3115
2921
  deformer,
3116
2922
  mesh
3117
2923
  });
3118
2924
  const ids = ARM_IDS[role];
3119
2925
  return [
3120
- part(role, ids.armDeformer, order, meshOf(band(0.5, seam), quad)),
2926
+ part(ids.cap, ids.forearmDeformer, order, {
2927
+ vertices,
2928
+ uvs,
2929
+ indices: [...fan, ...ring]
2930
+ }),
2931
+ part(role, ids.armDeformer, order + 1, meshOf(band(0.5, seam), quad)),
3121
2932
  part(
3122
2933
  ids.forearm,
3123
2934
  ids.forearmDeformer,
3124
- order + 1,
3125
- meshOf([...band(seam, -0.5), ...cap], [...quad, ...fan])
2935
+ order + 2,
2936
+ meshOf(band(seam, -0.5), quad)
3126
2937
  )
3127
2938
  ];
3128
2939
  }
3129
2940
  function armDeformers(role, g) {
3130
2941
  const ids = ARM_IDS[role];
3131
2942
  const params = ARM_PARAMS[role];
3132
- const rotate = (parameter) => ({
2943
+ const rotate = (parameter, range, sign) => ({
3133
2944
  parameter,
3134
2945
  channel: "rotate",
3135
- from: g.side * ARM_RANGE[0],
3136
- to: g.side * ARM_RANGE[1]
2946
+ from: sign * range[0],
2947
+ to: sign * range[1]
3137
2948
  });
3138
2949
  return [
3139
2950
  {
3140
2951
  id: ids.armDeformer,
3141
2952
  parent: BODY_WARP_ID,
3142
2953
  pivot: { ...g.shoulder },
3143
- bindings: [rotate(params.arm)]
2954
+ bindings: [rotate(params.arm, ARM_RANGE, g.side)]
3144
2955
  },
3145
2956
  {
3146
2957
  id: ids.forearmDeformer,
3147
2958
  parent: ids.armDeformer,
3148
2959
  pivot: { ...g.elbow },
3149
- bindings: [rotate(params.elbow)]
2960
+ bindings: [rotate(params.elbow, ELBOW_RANGE, -g.side)]
2961
+ }
2962
+ ];
2963
+ }
2964
+
2965
+ // src/auto-rig/roles.ts
2966
+ var ROLE_TABLE = [
2967
+ { role: "hair_back", family: "hair_back" },
2968
+ { role: "body", family: "body" },
2969
+ { role: "arm_L", family: "arm", parts: ["elbow_L", "arm_L", "forearm_L"] },
2970
+ { role: "arm_R", family: "arm", parts: ["elbow_R", "arm_R", "forearm_R"] },
2971
+ { role: "face", family: "face", required: true },
2972
+ { role: "blush_L", family: "face" },
2973
+ { role: "blush_R", family: "face" },
2974
+ { role: "nose", family: "nose" },
2975
+ { role: "mouth", family: "mouth", required: true },
2976
+ { role: "mouth_open", family: "mouth" },
2977
+ { role: "eye_L", family: "eye_L", required: true },
2978
+ { role: "eye_R", family: "eye_R", required: true },
2979
+ { role: "iris_L", family: "eye_L" },
2980
+ { role: "iris_R", family: "eye_R" },
2981
+ { role: "pupil_L", family: "eye_L" },
2982
+ { role: "pupil_R", family: "eye_R" },
2983
+ { role: "highlight_L", family: "eye_L" },
2984
+ { role: "highlight_R", family: "eye_R" },
2985
+ { role: "lash_lower_L", family: "eye_L" },
2986
+ { role: "lash_lower_R", family: "eye_R" },
2987
+ { role: "lash_L", family: "eye_L" },
2988
+ { role: "lash_R", family: "eye_R" },
2989
+ { role: "brow_L", family: "brow_L" },
2990
+ { role: "brow_R", family: "brow_R" },
2991
+ { role: "hair_front", family: "hair_front" }
2992
+ ];
2993
+ var REQUIRED_ROLES = ROLE_TABLE.filter(
2994
+ (r) => r.required
2995
+ ).map((r) => r.role);
2996
+ var SPEC_BY_ROLE = new Map(ROLE_TABLE.map((r, i) => [r.role, { ...r, i }]));
2997
+ function roleSpec(role) {
2998
+ const spec = SPEC_BY_ROLE.get(role);
2999
+ if (spec === void 0) throw new Error(`auto-rig: unknown role "${role}"`);
3000
+ return spec;
3001
+ }
3002
+ function partIdsOfRole(role) {
3003
+ return roleSpec(role).parts ?? [role];
3004
+ }
3005
+ var ROLE_BY_PART = new Map(
3006
+ ROLE_TABLE.flatMap((r) => (r.parts ?? []).map((p) => [p, r.role]))
3007
+ );
3008
+ function roleOfPart(partId) {
3009
+ return ROLE_BY_PART.get(partId) ?? partId;
3010
+ }
3011
+ var ALIASES = {
3012
+ eyebrow_L: "brow_L",
3013
+ eyebrow_R: "brow_R",
3014
+ eye_white_L: "eye_L",
3015
+ eye_white_R: "eye_R"
3016
+ };
3017
+ function roleOf(fileName) {
3018
+ const role = fileName.replace(/\.[^.]+$/, "").toLowerCase().replace(/[-\s]+/g, "_").replace(/_([lr])$/, (_, s) => `_${s.toUpperCase()}`);
3019
+ return ALIASES[role] ?? role;
3020
+ }
3021
+ function checkArmsHaveBody(roles) {
3022
+ const has = new Set(roles);
3023
+ if (has.has("body")) return;
3024
+ const arm = ROLE_TABLE.find((r) => r.family === "arm" && has.has(r.role));
3025
+ if (arm !== void 0) {
3026
+ throw new Error(
3027
+ `auto-rig: ${arm.role} needs a body layer (an arm hangs from the body's shoulder)`
3028
+ );
3029
+ }
3030
+ }
3031
+ function parseLayerRoles(fileNames) {
3032
+ const seen = /* @__PURE__ */ new Map();
3033
+ const out = [];
3034
+ for (const fileName of fileNames) {
3035
+ const role = roleOf(fileName);
3036
+ if (!SPEC_BY_ROLE.has(role)) {
3037
+ throw new Error(
3038
+ `auto-rig: "${fileName}" names no known role (expected one of ${ROLE_TABLE.map((r) => r.role).join(", ")})`
3039
+ );
3040
+ }
3041
+ const prior = seen.get(role);
3042
+ if (prior !== void 0) {
3043
+ throw new Error(
3044
+ `auto-rig: role "${role}" is named twice ("${prior}" and "${fileName}")`
3045
+ );
3150
3046
  }
3047
+ seen.set(role, fileName);
3048
+ out.push({ role, fileName });
3049
+ }
3050
+ const missing = REQUIRED_ROLES.filter((r) => !seen.has(r));
3051
+ if (missing.length > 0) {
3052
+ throw new Error(
3053
+ `auto-rig: missing required role${missing.length > 1 ? "s" : ""} ${missing.join(", ")}`
3054
+ );
3055
+ }
3056
+ checkArmsHaveBody(seen.keys());
3057
+ return out;
3058
+ }
3059
+
3060
+ // src/auto-rig/generate.ts
3061
+ var import_format8 = require("@ikijs/format");
3062
+
3063
+ // src/auto-rig/animations.ts
3064
+ var import_format6 = require("@ikijs/format");
3065
+ function browsY(y) {
3066
+ return [
3067
+ { parameter: import_format6.StandardParameter.BrowLeftY, value: y, blend: "add" },
3068
+ { parameter: import_format6.StandardParameter.BrowRightY, value: y, blend: "add" }
3151
3069
  ];
3152
3070
  }
3071
+ function browsTilt(innerDown) {
3072
+ return [
3073
+ { parameter: import_format6.StandardParameter.BrowLeftAngle, value: innerDown, blend: "add" },
3074
+ { parameter: import_format6.StandardParameter.BrowRightAngle, value: -innerDown, blend: "add" }
3075
+ ];
3076
+ }
3077
+ function cheek(value) {
3078
+ return { parameter: import_format6.StandardParameter.Cheek, value, blend: "add" };
3079
+ }
3080
+ var BROWS = [import_format6.StandardParameter.BrowLeftY, import_format6.StandardParameter.BrowRightY, import_format6.StandardParameter.BrowLeftAngle, import_format6.StandardParameter.BrowRightAngle];
3081
+ var EXPRESSIONS = [
3082
+ {
3083
+ id: "smile",
3084
+ description: "Smiling and pleased: warm, friendly, content. For greetings, thanks, agreement and gentle happiness.",
3085
+ parameters: [
3086
+ { parameter: import_format6.StandardParameter.MouthForm, value: 1, blend: "add" },
3087
+ ...browsY(0.25),
3088
+ cheek(0.4)
3089
+ ]
3090
+ },
3091
+ {
3092
+ id: "laugh",
3093
+ description: "Laughing, eyes shut and mouth open: delighted, amused. For jokes, playfulness and big joy.",
3094
+ parameters: [
3095
+ { parameter: import_format6.StandardParameter.EyeOpenLeft, value: 0, blend: "multiply" },
3096
+ { parameter: import_format6.StandardParameter.EyeOpenRight, value: 0, blend: "multiply" },
3097
+ { parameter: import_format6.StandardParameter.MouthForm, value: 1, blend: "add" },
3098
+ { parameter: import_format6.StandardParameter.MouthOpen, value: 0.8, blend: "overwrite" },
3099
+ ...browsY(0.4),
3100
+ cheek(0.65)
3101
+ ]
3102
+ },
3103
+ {
3104
+ id: "angry",
3105
+ description: "Angry or annoyed, frowning. For irritation, frustration, indignation or scolding.",
3106
+ needs: BROWS,
3107
+ parameters: [
3108
+ ...browsY(-0.5),
3109
+ ...browsTilt(0.8),
3110
+ { parameter: import_format6.StandardParameter.MouthForm, value: -0.9, blend: "add" }
3111
+ ]
3112
+ },
3113
+ {
3114
+ id: "sad",
3115
+ description: "Sad or disappointed, downcast. For sorrow, regret, apology or sympathy.",
3116
+ needs: BROWS,
3117
+ parameters: [
3118
+ ...browsY(0.35),
3119
+ ...browsTilt(-0.8),
3120
+ { parameter: import_format6.StandardParameter.MouthForm, value: -0.8, blend: "add" }
3121
+ ]
3122
+ },
3123
+ {
3124
+ id: "surprised",
3125
+ description: "Surprised, mouth dropped open: startled, amazed. For shock, sudden news or disbelief.",
3126
+ parameters: [
3127
+ ...browsY(0.8),
3128
+ { parameter: import_format6.StandardParameter.MouthOpen, value: 0.9, blend: "overwrite" }
3129
+ ]
3130
+ },
3131
+ {
3132
+ id: "shy",
3133
+ description: "Shy or embarrassed: bashful, flustered. For being praised, teased or caught off guard.",
3134
+ needs: [import_format6.StandardParameter.Cheek],
3135
+ parameters: [
3136
+ cheek(1),
3137
+ // Gaze down.
3138
+ { parameter: import_format6.StandardParameter.EyeballY, value: -0.65, blend: "add" },
3139
+ { parameter: import_format6.StandardParameter.MouthForm, value: 0.4, blend: "add" },
3140
+ ...browsTilt(-0.25)
3141
+ ]
3142
+ }
3143
+ ];
3144
+ function defaultExpressions(declared) {
3145
+ return EXPRESSIONS.filter(
3146
+ (e) => e.needs === void 0 || e.needs.some((id) => declared.has(id))
3147
+ ).map(({ needs: _needs, ...e }) => ({
3148
+ ...e,
3149
+ parameters: e.parameters.filter((t) => declared.has(t.parameter))
3150
+ }));
3151
+ }
3152
+ var DEFAULT_MOTIONS = {
3153
+ Nod: [
3154
+ {
3155
+ description: "Nods yes: agreement, acknowledgement, understanding.",
3156
+ duration: 1,
3157
+ curves: [
3158
+ {
3159
+ parameter: import_format6.StandardParameter.AngleY,
3160
+ keys: [
3161
+ [0, 0],
3162
+ [0.3, -18],
3163
+ [0.6, 4],
3164
+ [0.8, -1],
3165
+ [1, 0]
3166
+ ]
3167
+ }
3168
+ ]
3169
+ }
3170
+ ],
3171
+ Shake: [
3172
+ {
3173
+ description: "Shakes the head no: disagreement, refusal, disbelief.",
3174
+ duration: 1.2,
3175
+ // The first swing peaks at 0.2 s, which the default fade would still
3176
+ // be damping.
3177
+ fadeIn: 0.15,
3178
+ curves: [
3179
+ {
3180
+ parameter: import_format6.StandardParameter.AngleX,
3181
+ keys: [
3182
+ [0, 0],
3183
+ [0.2, -18],
3184
+ [0.45, 18],
3185
+ [0.7, -16],
3186
+ [0.95, 10],
3187
+ [1.2, 0]
3188
+ ]
3189
+ }
3190
+ ]
3191
+ }
3192
+ ],
3193
+ Tilt: [
3194
+ {
3195
+ description: "Tilts the head to one side and back: curiosity, puzzlement, a question.",
3196
+ duration: 1.4,
3197
+ curves: [
3198
+ {
3199
+ parameter: import_format6.StandardParameter.AngleZ,
3200
+ keys: [
3201
+ [0, 0],
3202
+ [0.35, 20],
3203
+ [1, 20],
3204
+ [1.4, 0]
3205
+ ]
3206
+ }
3207
+ ]
3208
+ }
3209
+ ]
3210
+ };
3153
3211
 
3154
3212
  // src/auto-rig/checks.ts
3155
3213
  var finite = (v) => typeof v === "number" && Number.isFinite(v);
@@ -5722,13 +5780,14 @@ function declareParameters(roles) {
5722
5780
  add(import_format8.StandardParameter.BodyAngleZ, "Body Angle Z", -10, 10, 0);
5723
5781
  }
5724
5782
  const [armMin, armMax] = ARM_RANGE;
5783
+ const [elbowMin, elbowMax] = ELBOW_RANGE;
5725
5784
  if (roles.has("arm_L")) {
5726
5785
  add(import_format8.StandardParameter.ArmLeft, "Arm L", armMin, armMax, 0);
5727
- add(import_format8.StandardParameter.ElbowLeft, "Elbow L", armMin, armMax, 0);
5786
+ add(import_format8.StandardParameter.ElbowLeft, "Elbow L", elbowMin, elbowMax, 0);
5728
5787
  }
5729
5788
  if (roles.has("arm_R")) {
5730
5789
  add(import_format8.StandardParameter.ArmRight, "Arm R", armMin, armMax, 0);
5731
- add(import_format8.StandardParameter.ElbowRight, "Elbow R", armMin, armMax, 0);
5790
+ add(import_format8.StandardParameter.ElbowRight, "Elbow R", elbowMin, elbowMax, 0);
5732
5791
  }
5733
5792
  add(import_format8.StandardParameter.EyeOpenLeft, "Eye L", 0, 1, 1);
5734
5793
  add(import_format8.StandardParameter.EyeOpenRight, "Eye R", 0, 1, 1);
@@ -5790,6 +5849,7 @@ function declareParameters(roles) {
5790
5849
  SetPhysicsRig,
5791
5850
  TurnTargetError,
5792
5851
  UV_INSET_PX,
5852
+ armGeometry,
5793
5853
  captureBindingEndpoint,
5794
5854
  computeGridOffsets,
5795
5855
  createDefaultMatrixDeformer,