@ikijs/editor 0.16.1 → 0.18.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,
@@ -51,6 +52,7 @@ __export(index_exports, {
51
52
  SetPhysicsRig: () => SetPhysicsRig,
52
53
  TurnTargetError: () => TurnTargetError,
53
54
  UV_INSET_PX: () => UV_INSET_PX,
55
+ armGeometry: () => armGeometry,
54
56
  captureBindingEndpoint: () => captureBindingEndpoint,
55
57
  computeGridOffsets: () => computeGridOffsets,
56
58
  createDefaultMatrixDeformer: () => createDefaultMatrixDeformer,
@@ -67,6 +69,7 @@ __export(index_exports, {
67
69
  isSpeckCore: () => isSpeckCore,
68
70
  packAtlas: () => packAtlas,
69
71
  parseLayerRoles: () => parseLayerRoles,
72
+ partIdsOfRole: () => partIdsOfRole,
70
73
  upsertGridKeyform: () => upsertGridKeyform,
71
74
  uvRectFor: () => uvRectFor,
72
75
  validateDeformerDelete: () => validateDeformerDelete,
@@ -1632,6 +1635,14 @@ function facePlateHalfOf(layers) {
1632
1635
  return face.cropW / 2;
1633
1636
  }
1634
1637
  var STRAND_MIN_RUN_FRACTION = 0.5;
1638
+ var ROW_RUN_ROLES = /* @__PURE__ */ new Set([
1639
+ "face",
1640
+ "hair_front",
1641
+ "hair_back",
1642
+ "body",
1643
+ "arm_L",
1644
+ "arm_R"
1645
+ ]);
1635
1646
  function createLayerSetMeasurer(canvas) {
1636
1647
  for (const field of ["width", "height"]) {
1637
1648
  const value = canvas[field];
@@ -1661,7 +1672,7 @@ function createLayerSetMeasurer(canvas) {
1661
1672
  if (bbox === null) return null;
1662
1673
  const rowLeft = new Int32Array(canvasH).fill(canvasW);
1663
1674
  const rowRight = new Int32Array(canvasH).fill(-1);
1664
- const rowRuns = role === "face" || role === "hair_front" || role === "hair_back" ? [] : void 0;
1675
+ const rowRuns = ROW_RUN_ROLES.has(role) ? [] : void 0;
1665
1676
  for (let y = 0; y < canvasH; y++) {
1666
1677
  let runs;
1667
1678
  if (rowRuns !== void 0 && y >= bbox.y && y < bbox.y + bbox.h) {
@@ -2003,7 +2014,13 @@ var PROFILE_VALUES = {
2003
2014
  brow: 0.15,
2004
2015
  breath: 0.03,
2005
2016
  sway: 0.08,
2006
- blushRest: 0.4
2017
+ blushRest: 0.4,
2018
+ bodySlide: 0.1,
2019
+ bodyNarrow: 0.05,
2020
+ bodyBow: 0.06,
2021
+ bodyRoll: 4,
2022
+ bodyFollowX: 0.3,
2023
+ bodyFollowZ: 0.3
2007
2024
  };
2008
2025
  function deriveProfile(v) {
2009
2026
  const farLead = v.lead * (1 - v.fore / 2);
@@ -2097,9 +2114,32 @@ function deriveProfile(v) {
2097
2114
  * slide, to the next 0.05. */
2098
2115
  hiddenNeck: Math.ceil(30 * (v.slide + v.lead) - 1e-9) / 20
2099
2116
  };
2100
- return { TURN: turn, NOD: nod, ROLL_DEG: v.rollDeg, AMPLITUDE: amplitude };
2117
+ const body = {
2118
+ /** BodyAngleX: the upper body's slide, hh. */
2119
+ slide: v.bodySlide,
2120
+ /** BodyAngleX: how much narrower the upper body gets, either way. */
2121
+ narrow: v.bodyNarrow,
2122
+ /** BodyAngleY: the upper body's rise (+) or bow (−), hh. */
2123
+ bow: v.bodyBow,
2124
+ /** BodyAngleZ: the upper body's roll about the hips, degrees. */
2125
+ rollDeg: v.bodyRoll,
2126
+ /** AngleX: the body's follow, as a share of BodyAngleX's motion. */
2127
+ followX: v.bodyFollowX,
2128
+ /** AngleZ: the body's follow, as a share of its roll. */
2129
+ followZ: v.bodyFollowZ,
2130
+ /** The roll the follow gives the body at AngleZ ±30, degrees: β, the
2131
+ * share of `ROLL_DEG` the head's own roll leaves to the body. */
2132
+ followRoll: v.bodyFollowZ * v.bodyRoll
2133
+ };
2134
+ return {
2135
+ TURN: turn,
2136
+ NOD: nod,
2137
+ ROLL_DEG: v.rollDeg,
2138
+ AMPLITUDE: amplitude,
2139
+ BODY: body
2140
+ };
2101
2141
  }
2102
- var { TURN, NOD, ROLL_DEG, AMPLITUDE } = deriveProfile(PROFILE_VALUES);
2142
+ var { TURN, NOD, ROLL_DEG, AMPLITUDE, BODY } = deriveProfile(PROFILE_VALUES);
2103
2143
  var STYLE_KNOBS = [
2104
2144
  "turn",
2105
2145
  "featureLead",
@@ -2129,236 +2169,23 @@ var TurnTargetError = class extends Error {
2129
2169
  this.name = "TurnTargetError";
2130
2170
  }
2131
2171
  };
2172
+ var LayerGeometryError = class extends Error {
2173
+ constructor(message) {
2174
+ super(message);
2175
+ this.name = "LayerGeometryError";
2176
+ }
2177
+ };
2132
2178
  var DEFAULT_TURN_TARGETS = Object.freeze({
2133
2179
  eyeShift: round2((TURN.eyeFar + TURN.eyeNear) / 2 - TURN.hairBack),
2134
2180
  farEyeRatio: round2(TURN.eyeFarScale / TURN.eyeNearScale),
2135
2181
  silhouetteRatio: 1
2136
2182
  });
2137
2183
 
2138
- // src/auto-rig/roles.ts
2139
- var ROLE_TABLE = [
2140
- { role: "hair_back", family: "hair_back" },
2141
- { role: "body", family: "body" },
2142
- { role: "face", family: "face", required: true },
2143
- { role: "blush_L", family: "face" },
2144
- { role: "blush_R", family: "face" },
2145
- { role: "nose", family: "nose" },
2146
- { role: "mouth", family: "mouth", required: true },
2147
- { role: "mouth_open", family: "mouth" },
2148
- { role: "eye_L", family: "eye_L", required: true },
2149
- { role: "eye_R", family: "eye_R", required: true },
2150
- { role: "iris_L", family: "eye_L" },
2151
- { role: "iris_R", family: "eye_R" },
2152
- { role: "pupil_L", family: "eye_L" },
2153
- { role: "pupil_R", family: "eye_R" },
2154
- { role: "highlight_L", family: "eye_L" },
2155
- { role: "highlight_R", family: "eye_R" },
2156
- { role: "lash_lower_L", family: "eye_L" },
2157
- { role: "lash_lower_R", family: "eye_R" },
2158
- { role: "lash_L", family: "eye_L" },
2159
- { role: "lash_R", family: "eye_R" },
2160
- { role: "brow_L", family: "brow_L" },
2161
- { role: "brow_R", family: "brow_R" },
2162
- { role: "hair_front", family: "hair_front" }
2163
- ];
2164
- var REQUIRED_ROLES = ROLE_TABLE.filter(
2165
- (r) => r.required
2166
- ).map((r) => r.role);
2167
- var SPEC_BY_ROLE = new Map(ROLE_TABLE.map((r, i) => [r.role, { ...r, i }]));
2168
- function roleSpec(role) {
2169
- const spec = SPEC_BY_ROLE.get(role);
2170
- if (spec === void 0) throw new Error(`auto-rig: unknown role "${role}"`);
2171
- return spec;
2172
- }
2173
- var ALIASES = {
2174
- eyebrow_L: "brow_L",
2175
- eyebrow_R: "brow_R",
2176
- eye_white_L: "eye_L",
2177
- eye_white_R: "eye_R"
2178
- };
2179
- function roleOf(fileName) {
2180
- const role = fileName.replace(/\.[^.]+$/, "").toLowerCase().replace(/[-\s]+/g, "_").replace(/_([lr])$/, (_, s) => `_${s.toUpperCase()}`);
2181
- return ALIASES[role] ?? role;
2182
- }
2183
- function parseLayerRoles(fileNames) {
2184
- const seen = /* @__PURE__ */ new Map();
2185
- const out = [];
2186
- for (const fileName of fileNames) {
2187
- const role = roleOf(fileName);
2188
- if (!SPEC_BY_ROLE.has(role)) {
2189
- throw new Error(
2190
- `auto-rig: "${fileName}" names no known role (expected one of ${ROLE_TABLE.map((r) => r.role).join(", ")})`
2191
- );
2192
- }
2193
- const prior = seen.get(role);
2194
- if (prior !== void 0) {
2195
- throw new Error(
2196
- `auto-rig: role "${role}" is named twice ("${prior}" and "${fileName}")`
2197
- );
2198
- }
2199
- seen.set(role, fileName);
2200
- out.push({ role, fileName });
2201
- }
2202
- const missing = REQUIRED_ROLES.filter((r) => !seen.has(r));
2203
- if (missing.length > 0) {
2204
- throw new Error(
2205
- `auto-rig: missing required role${missing.length > 1 ? "s" : ""} ${missing.join(", ")}`
2206
- );
2207
- }
2208
- return out;
2209
- }
2210
-
2211
- // src/auto-rig/generate.ts
2212
- var import_format6 = require("@ikijs/format");
2184
+ // src/auto-rig/arms.ts
2185
+ var import_format5 = require("@ikijs/format");
2213
2186
 
2214
- // src/auto-rig/animations.ts
2215
- var import_format3 = require("@ikijs/format");
2216
- function browsY(y) {
2217
- return [
2218
- { parameter: import_format3.StandardParameter.BrowLeftY, value: y, blend: "add" },
2219
- { parameter: import_format3.StandardParameter.BrowRightY, value: y, blend: "add" }
2220
- ];
2221
- }
2222
- function browsTilt(innerDown) {
2223
- return [
2224
- { parameter: import_format3.StandardParameter.BrowLeftAngle, value: innerDown, blend: "add" },
2225
- { parameter: import_format3.StandardParameter.BrowRightAngle, value: -innerDown, blend: "add" }
2226
- ];
2227
- }
2228
- function cheek(value) {
2229
- return { parameter: import_format3.StandardParameter.Cheek, value, blend: "add" };
2230
- }
2231
- var BROWS = [import_format3.StandardParameter.BrowLeftY, import_format3.StandardParameter.BrowRightY, import_format3.StandardParameter.BrowLeftAngle, import_format3.StandardParameter.BrowRightAngle];
2232
- var EXPRESSIONS = [
2233
- {
2234
- id: "smile",
2235
- description: "Smiling and pleased: warm, friendly, content. For greetings, thanks, agreement and gentle happiness.",
2236
- parameters: [
2237
- { parameter: import_format3.StandardParameter.MouthForm, value: 1, blend: "add" },
2238
- ...browsY(0.25),
2239
- cheek(0.4)
2240
- ]
2241
- },
2242
- {
2243
- id: "laugh",
2244
- description: "Laughing, eyes shut and mouth open: delighted, amused. For jokes, playfulness and big joy.",
2245
- parameters: [
2246
- { parameter: import_format3.StandardParameter.EyeOpenLeft, value: 0, blend: "multiply" },
2247
- { parameter: import_format3.StandardParameter.EyeOpenRight, value: 0, blend: "multiply" },
2248
- { parameter: import_format3.StandardParameter.MouthForm, value: 1, blend: "add" },
2249
- { parameter: import_format3.StandardParameter.MouthOpen, value: 0.8, blend: "overwrite" },
2250
- ...browsY(0.4),
2251
- cheek(0.65)
2252
- ]
2253
- },
2254
- {
2255
- id: "angry",
2256
- description: "Angry or annoyed, frowning. For irritation, frustration, indignation or scolding.",
2257
- needs: BROWS,
2258
- parameters: [
2259
- ...browsY(-0.5),
2260
- ...browsTilt(0.8),
2261
- { parameter: import_format3.StandardParameter.MouthForm, value: -0.9, blend: "add" }
2262
- ]
2263
- },
2264
- {
2265
- id: "sad",
2266
- description: "Sad or disappointed, downcast. For sorrow, regret, apology or sympathy.",
2267
- needs: BROWS,
2268
- parameters: [
2269
- ...browsY(0.35),
2270
- ...browsTilt(-0.8),
2271
- { parameter: import_format3.StandardParameter.MouthForm, value: -0.8, blend: "add" }
2272
- ]
2273
- },
2274
- {
2275
- id: "surprised",
2276
- description: "Surprised, mouth dropped open: startled, amazed. For shock, sudden news or disbelief.",
2277
- parameters: [
2278
- ...browsY(0.8),
2279
- { parameter: import_format3.StandardParameter.MouthOpen, value: 0.9, blend: "overwrite" }
2280
- ]
2281
- },
2282
- {
2283
- id: "shy",
2284
- description: "Shy or embarrassed: bashful, flustered. For being praised, teased or caught off guard.",
2285
- needs: [import_format3.StandardParameter.Cheek],
2286
- parameters: [
2287
- cheek(1),
2288
- // Gaze down.
2289
- { parameter: import_format3.StandardParameter.EyeballY, value: -0.65, blend: "add" },
2290
- { parameter: import_format3.StandardParameter.MouthForm, value: 0.4, blend: "add" },
2291
- ...browsTilt(-0.25)
2292
- ]
2293
- }
2294
- ];
2295
- function defaultExpressions(declared) {
2296
- return EXPRESSIONS.filter(
2297
- (e) => e.needs === void 0 || e.needs.some((id) => declared.has(id))
2298
- ).map(({ needs: _needs, ...e }) => ({
2299
- ...e,
2300
- parameters: e.parameters.filter((t) => declared.has(t.parameter))
2301
- }));
2302
- }
2303
- var DEFAULT_MOTIONS = {
2304
- Nod: [
2305
- {
2306
- description: "Nods yes: agreement, acknowledgement, understanding.",
2307
- duration: 1,
2308
- curves: [
2309
- {
2310
- parameter: import_format3.StandardParameter.AngleY,
2311
- keys: [
2312
- [0, 0],
2313
- [0.3, -18],
2314
- [0.6, 4],
2315
- [0.8, -1],
2316
- [1, 0]
2317
- ]
2318
- }
2319
- ]
2320
- }
2321
- ],
2322
- Shake: [
2323
- {
2324
- description: "Shakes the head no: disagreement, refusal, disbelief.",
2325
- duration: 1.2,
2326
- // The first swing peaks at 0.2 s, which the default fade would still
2327
- // be damping.
2328
- fadeIn: 0.15,
2329
- curves: [
2330
- {
2331
- parameter: import_format3.StandardParameter.AngleX,
2332
- keys: [
2333
- [0, 0],
2334
- [0.2, -18],
2335
- [0.45, 18],
2336
- [0.7, -16],
2337
- [0.95, 10],
2338
- [1.2, 0]
2339
- ]
2340
- }
2341
- ]
2342
- }
2343
- ],
2344
- Tilt: [
2345
- {
2346
- description: "Tilts the head to one side and back: curiosity, puzzlement, a question.",
2347
- duration: 1.4,
2348
- curves: [
2349
- {
2350
- parameter: import_format3.StandardParameter.AngleZ,
2351
- keys: [
2352
- [0, 0],
2353
- [0.35, 20],
2354
- [1, 20],
2355
- [1.4, 0]
2356
- ]
2357
- }
2358
- ]
2359
- }
2360
- ]
2361
- };
2187
+ // src/auto-rig/body.ts
2188
+ var import_format4 = require("@ikijs/format");
2362
2189
 
2363
2190
  // src/auto-rig/layout.ts
2364
2191
  function boxOfLayer(layer) {
@@ -2433,49 +2260,941 @@ function meshPoints(mesh, box) {
2433
2260
  return out;
2434
2261
  }
2435
2262
 
2436
- // src/auto-rig/checks.ts
2437
- var finite = (v) => typeof v === "number" && Number.isFinite(v);
2438
- var isObject = (v) => typeof v === "object" && v !== null;
2439
- var MIN_FACE_PX = 24;
2440
- function checkLayers(layers, canvas) {
2441
- if (!Array.isArray(layers) || layers.length === 0) {
2442
- throw new Error(
2443
- "auto-rig: generateIkiFromLayerSet: layers must be a non-empty array"
2444
- );
2445
- }
2446
- if (!(finite(canvas?.width) && canvas.width > 0)) {
2447
- throw new Error("auto-rig: canvas.width must be a positive number");
2448
- }
2449
- if (!(finite(canvas?.height) && canvas.height > 0)) {
2450
- throw new Error("auto-rig: canvas.height must be a positive number");
2451
- }
2452
- const seen = /* @__PURE__ */ new Set();
2453
- for (const l of layers) {
2454
- roleSpec(l.role);
2455
- if (seen.has(l.role)) {
2456
- throw new Error(`auto-rig: role "${l.role}" appears twice`);
2457
- }
2458
- seen.add(l.role);
2459
- const at = `auto-rig: layer "${l.fileName}"`;
2460
- const { x, y, w, h } = l.bbox ?? {};
2461
- if (![x, y, w, h].every(finite) || w <= 0 || h <= 0 || x < 0 || y < 0 || x + w > canvas.width || y + h > canvas.height) {
2462
- throw new Error(
2463
- `${at} has a bbox outside its ${canvas.width}x${canvas.height} canvas`
2464
- );
2465
- }
2466
- if (l.canvasW !== canvas.width || l.canvasH !== canvas.height) {
2467
- throw new Error(
2468
- `${at} was measured on a ${l.canvasW}x${l.canvasH} canvas, not ${canvas.width}x${canvas.height}`
2469
- );
2470
- }
2471
- if (l.cropW !== w || l.cropH !== h) {
2472
- throw new Error(
2473
- `${at}: crop ${l.cropW}x${l.cropH} is not its bbox ${w}x${h}`
2474
- );
2475
- }
2476
- if (l.role === "face" && (w < MIN_FACE_PX || h < MIN_FACE_PX)) {
2477
- throw new Error(
2478
- `${at}: the face is ${w}x${h} px; the face plate needs at least ${MIN_FACE_PX} px each way to be meshed`
2263
+ // src/auto-rig/drivers.ts
2264
+ var import_format3 = require("@ikijs/format");
2265
+ var SWAY_RANGE = 20;
2266
+ var SWAY_CURVE = 1.4;
2267
+ var HAIR_HANG = 0.35;
2268
+ var Z_STOPS = [-30, 0, 30];
2269
+ function localWarp(parameter, mesh, box, values, f) {
2270
+ const pts = meshPoints(mesh, box);
2271
+ const w = bw(box);
2272
+ const h = bh(box);
2273
+ return {
2274
+ parameter,
2275
+ keyforms: values.map((value) => ({
2276
+ value,
2277
+ offsets: pts.flatMap((p, i) => {
2278
+ const [dx, dy] = f(p, value, i);
2279
+ return [roundTo(dx / w, 1e-4), roundTo(dy / h, 1e-4)];
2280
+ })
2281
+ }))
2282
+ };
2283
+ }
2284
+ var rollOf = (v, deg = ROLL_DEG) => -v / 30 * deg * DEG;
2285
+ function motionReach(warps, mesh, box, extra = [0, 0]) {
2286
+ const out = {
2287
+ left: extra[0],
2288
+ right: extra[0],
2289
+ bottom: extra[1],
2290
+ top: extra[1]
2291
+ };
2292
+ if (mesh === void 0) return out;
2293
+ const pts = meshPoints(mesh, box);
2294
+ for (const warp of warps ?? []) {
2295
+ let l = 0;
2296
+ let r = 0;
2297
+ let b = 0;
2298
+ let t = 0;
2299
+ for (const k of warp.keyforms) {
2300
+ pts.forEach(([x, y], i) => {
2301
+ const px = x + k.offsets[2 * i] * bw(box);
2302
+ const py = y + k.offsets[2 * i + 1] * bh(box);
2303
+ l = Math.max(l, box.x0 - px);
2304
+ r = Math.max(r, px - box.x1);
2305
+ b = Math.max(b, box.y0 - py);
2306
+ t = Math.max(t, py - box.y1);
2307
+ });
2308
+ }
2309
+ out.left += l;
2310
+ out.right += r;
2311
+ out.bottom += b;
2312
+ out.top += t;
2313
+ }
2314
+ return out;
2315
+ }
2316
+ function blinkFold(param, mesh, box, travel, eye = box) {
2317
+ const yc = eye.y1 - travel * bh(eye);
2318
+ return localWarp(
2319
+ param,
2320
+ mesh,
2321
+ box,
2322
+ [0, 1],
2323
+ ([, y], v) => v === 1 ? [0, 0] : [0, yc - y]
2324
+ );
2325
+ }
2326
+ function lashFold(param, mesh, lash, eye, travel) {
2327
+ const yc = eye.y1 - travel * bh(eye) - 0.04 * bh(eye);
2328
+ const c = cx(lash);
2329
+ const half = bw(lash) / 2;
2330
+ const sag = 0.3 * bh(lash);
2331
+ return localWarp(param, mesh, lash, [0, 1], ([x, y], v) => {
2332
+ if (v === 1) return [0, 0];
2333
+ const u = (x - c) / half;
2334
+ return [0, yc + (y - lash.y0) * 0.5 - sag * (1 - u * u) - y];
2335
+ });
2336
+ }
2337
+ function gazeBindings(irisW, share = 1) {
2338
+ const gx = AMPLITUDE.gazeX * irisW * share;
2339
+ const gy = AMPLITUDE.gazeY * irisW * share;
2340
+ return [
2341
+ {
2342
+ parameter: import_format3.StandardParameter.EyeballX,
2343
+ channel: "translateX",
2344
+ from: -round1(gx),
2345
+ to: round1(gx)
2346
+ },
2347
+ {
2348
+ parameter: import_format3.StandardParameter.EyeballY,
2349
+ channel: "translateY",
2350
+ from: -round1(gy),
2351
+ to: round1(gy)
2352
+ }
2353
+ ];
2354
+ }
2355
+ function gazeReach(irisW, share = 1) {
2356
+ return [AMPLITUDE.gazeX * irisW * share, AMPLITUDE.gazeY * irisW * share];
2357
+ }
2358
+ function browReach(hh) {
2359
+ return AMPLITUDE.brow * hh;
2360
+ }
2361
+ function browBindings(side, reach) {
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;
2364
+ return [
2365
+ {
2366
+ parameter: y,
2367
+ channel: "translateY",
2368
+ from: -round1(reach),
2369
+ to: round1(reach)
2370
+ },
2371
+ { parameter: a, channel: "rotate", from: -BROW_TILT, to: BROW_TILT }
2372
+ ];
2373
+ }
2374
+ var BROW_TILT = 15;
2375
+ function mouthForm(mesh, box) {
2376
+ const a = 0.3 * bh(box);
2377
+ const c = cx(box);
2378
+ const half = bw(box) / 2;
2379
+ return localWarp(import_format3.StandardParameter.MouthForm, mesh, box, [-1, 0, 1], ([x], v) => {
2380
+ if (v === 0) return [0, 0];
2381
+ const u = (x - c) / half;
2382
+ const widen = v > 0 ? 0.05 : 0.03;
2383
+ return [v * widen * (x - c), v * a * (u * u - 0.3)];
2384
+ });
2385
+ }
2386
+ function mouthOpenGrow(mesh, box) {
2387
+ const c = cx(box);
2388
+ return localWarp(
2389
+ import_format3.StandardParameter.MouthOpen,
2390
+ mesh,
2391
+ box,
2392
+ [0, 1],
2393
+ ([x, y], v) => v === 1 ? [(AMPLITUDE.mouthOpenWidth - 1) * (x - c), 0] : [0, box.y1 + (y - box.y1) * 0.2 - y]
2394
+ );
2395
+ }
2396
+ function mouthWiden(mesh, box) {
2397
+ const c = cx(box);
2398
+ return localWarp(import_format3.StandardParameter.MouthOpen, mesh, box, [0, 1], ([x], v) => [
2399
+ v * (AMPLITUDE.mouthOpenWidth - 1) * (x - c),
2400
+ 0
2401
+ ]);
2402
+ }
2403
+ function hairSway(param, mesh, box, amp) {
2404
+ return localWarp(
2405
+ param,
2406
+ mesh,
2407
+ box,
2408
+ [-SWAY_RANGE, 0, SWAY_RANGE],
2409
+ ([, y], v) => {
2410
+ const t = Math.max(0, (box.y1 - y) / bh(box));
2411
+ return [v / SWAY_RANGE * amp * t ** SWAY_CURVE, 0];
2412
+ }
2413
+ );
2414
+ }
2415
+ function tiltHang(mesh, box, pivot, weight, deg = ROLL_DEG) {
2416
+ return localWarp(import_format3.StandardParameter.AngleZ, mesh, box, Z_STOPS, ([x, y], v, i) => {
2417
+ const g = weight(y, i);
2418
+ if (g === 0 || v === 0) return [0, 0];
2419
+ const a = -g * rollOf(v, deg);
2420
+ const px = x - pivot[0];
2421
+ const py = y - pivot[1];
2422
+ return [
2423
+ pivot[0] + px * Math.cos(a) - py * Math.sin(a) - x,
2424
+ pivot[1] + px * Math.sin(a) + py * Math.cos(a) - y
2425
+ ];
2426
+ });
2427
+ }
2428
+ function hairHangWeight(pivotY, bottom) {
2429
+ if (bottom >= pivotY) return () => 0;
2430
+ return (y) => HAIR_HANG * smoothstep(pivotY, bottom, y);
2431
+ }
2432
+ var round1 = (v) => roundTo(v, 0.1);
2433
+
2434
+ // src/auto-rig/grid.ts
2435
+ var X_STOPS = [-30, 0, 30];
2436
+ var Y_STOPS = [-30, 0, 30];
2437
+ function lattice(box, cell, min, max) {
2438
+ const cols = cellsFor(box.x1 - box.x0, cell, min, max);
2439
+ const rows = cellsFor(box.y1 - box.y0, cell, min, max);
2440
+ const points = [];
2441
+ for (let r = 0; r <= rows; r++) {
2442
+ const y = box.y1 - (box.y1 - box.y0) * r / rows;
2443
+ for (let c = 0; c <= cols; c++) {
2444
+ points.push(
2445
+ roundTo(box.x0 + (box.x1 - box.x0) * c / cols, 0.01),
2446
+ roundTo(y, 0.01)
2447
+ );
2448
+ }
2449
+ }
2450
+ return { box, cols, rows, points };
2451
+ }
2452
+ function bake(l, field) {
2453
+ const out = [];
2454
+ for (const ay of Y_STOPS) {
2455
+ for (const ax of X_STOPS) {
2456
+ const offsets = [];
2457
+ for (let k = 0; k < l.points.length; k += 2) {
2458
+ if (ax === 0 && ay === 0) {
2459
+ offsets.push(0, 0);
2460
+ continue;
2461
+ }
2462
+ const [dx, dy] = field(l.points[k], l.points[k + 1], ax, ay);
2463
+ offsets.push(roundTo(dx, 0.1), roundTo(dy, 0.1));
2464
+ }
2465
+ out.push(offsets);
2466
+ }
2467
+ }
2468
+ return out;
2469
+ }
2470
+ function keyformAt(keyforms, ax) {
2471
+ const j = Y_STOPS.indexOf(0);
2472
+ const row = X_STOPS.map((_, i) => keyforms[j * X_STOPS.length + i]);
2473
+ return blendStops(X_STOPS, row, ax);
2474
+ }
2475
+ function blendStops(values, offsets, v) {
2476
+ const x = clamp(v, values[0], values[values.length - 1]);
2477
+ let i = 0;
2478
+ while (i < values.length - 2 && x > values[i + 1]) i++;
2479
+ const t = (x - values[i]) / (values[i + 1] - values[i]);
2480
+ return offsets[i].map((o, k) => o + (offsets[i + 1][k] - o) * t);
2481
+ }
2482
+ function land(l, offsets, x, y) {
2483
+ const { cols, rows, points } = l;
2484
+ const stride = cols + 1;
2485
+ let col = cols - 1;
2486
+ for (let c = 0; c < cols; c++) {
2487
+ if (x < points[(c + 1) * 2]) {
2488
+ col = c;
2489
+ break;
2490
+ }
2491
+ }
2492
+ let row = rows - 1;
2493
+ for (let r = 0; r < rows; r++) {
2494
+ if (y > points[(r + 1) * stride * 2 + 1]) {
2495
+ row = r;
2496
+ break;
2497
+ }
2498
+ }
2499
+ const x0 = points[col * 2];
2500
+ const x1 = points[(col + 1) * 2];
2501
+ const yT = points[row * stride * 2 + 1];
2502
+ const yB = points[(row + 1) * stride * 2 + 1];
2503
+ const s = clamp((x - x0) / (x1 - x0), 0, 1);
2504
+ const t = clamp((yT - y) / (yT - yB), 0, 1);
2505
+ const at = (r, c) => {
2506
+ const k = (r * stride + c) * 2;
2507
+ return [points[k] + offsets[k], points[k + 1] + offsets[k + 1]];
2508
+ };
2509
+ const p00 = at(row, col);
2510
+ const p10 = at(row, col + 1);
2511
+ const p01 = at(row + 1, col);
2512
+ const p11 = at(row + 1, col + 1);
2513
+ const topX = p00[0] + (p10[0] - p00[0]) * s;
2514
+ const topY = p00[1] + (p10[1] - p00[1]) * s;
2515
+ const botX = p01[0] + (p11[0] - p01[0]) * s;
2516
+ const botY = p01[1] + (p11[1] - p01[1]) * s;
2517
+ return [topX + (botX - topX) * t, topY + (botY - topY) * t];
2518
+ }
2519
+
2520
+ // src/auto-rig/body.ts
2521
+ var BODY_WARP_ID = "bodyWarp";
2522
+ var BODY_GRID_PAD = 6;
2523
+ var CHEST_DROP = 0.8;
2524
+ var HIP_BAND = [0.35, 0.75];
2525
+ var LEG_MIN = 0.1;
2526
+ var HIP_FRACTION = 0.5;
2527
+ var BODY_MESH_PX = 48;
2528
+ var BODY_STOPS = [-10, 0, 10];
2529
+ var FOLD_MARGIN = 0.01;
2530
+ function hipLine(layer) {
2531
+ const { bbox, canvasH, rowRuns } = layer;
2532
+ const top = canvasH / 2 - bbox.y;
2533
+ if (rowRuns !== void 0) {
2534
+ const legMin = LEG_MIN * bbox.w;
2535
+ const last = Math.floor(HIP_BAND[1] * bbox.h);
2536
+ for (let k = Math.ceil(HIP_BAND[0] * bbox.h); k <= last; k++) {
2537
+ const runs = rowRuns[k];
2538
+ let legs = 0;
2539
+ for (let i = 0; i < runs.length; i += 2) {
2540
+ if (runs[i + 1] - runs[i] >= legMin) legs++;
2541
+ }
2542
+ if (legs >= 2) return roundTo(top - k, 0.01);
2543
+ }
2544
+ }
2545
+ if (bbox.y + bbox.h === canvasH) return roundTo(top - bbox.h, 0.01);
2546
+ return roundTo(top - HIP_FRACTION * bbox.h, 0.01);
2547
+ }
2548
+ function bodyLattice(body, chin, hh, pivots, anchor) {
2549
+ const b = boxOfLayer(body);
2550
+ const g = (v) => Math.round(roundTo(v, 0.01) * 100);
2551
+ const pad = BODY_GRID_PAD * 100;
2552
+ const all = [chin, ...pivots];
2553
+ const a = g(anchor);
2554
+ const p = Math.min(...all.map((q) => g(q.y)));
2555
+ if (p - a < 2) {
2556
+ throw new LayerGeometryError(
2557
+ `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`
2558
+ );
2559
+ }
2560
+ let full = Math.min(g(chin.y - CHEST_DROP * hh), p - pad);
2561
+ if (full - a < pad) {
2562
+ full = Math.min(Math.max(Math.round((a + p) / 2), a + 1), p - 1);
2563
+ }
2564
+ const top = Math.max(g(b.y1), ...all.map((q) => g(q.y))) + pad;
2565
+ const bottom = Math.min(a, g(b.y0)) - pad;
2566
+ const split = (from, to, cells) => {
2567
+ const n = Math.max(1, Math.min(cells, from - to));
2568
+ const out = [from];
2569
+ for (let i = 1; i < n; i++) {
2570
+ out.push(Math.round(from - (from - to) * i / n));
2571
+ }
2572
+ out.push(to);
2573
+ return out;
2574
+ };
2575
+ const bandA = split(top, full, cellsFor((top - full) / 100, 96, 1, 8));
2576
+ const ramp = split(full, a, cellsFor((full - a) / 100, 96, 2, 8));
2577
+ const rows = [...bandA, ...ramp.slice(1, -1)];
2578
+ const ys = [...rows.map((y) => y / 100), anchor, bottom / 100];
2579
+ const weights = [...rows.map((y) => smoothstep(a, full, y)), 0, 0];
2580
+ const x0 = Math.min(b.x0, ...all.map((q) => q.x)) - BODY_GRID_PAD;
2581
+ const x1 = Math.max(b.x1, ...all.map((q) => q.x)) + BODY_GRID_PAD;
2582
+ const cols = cellsFor(x1 - x0, 96, 2, 8);
2583
+ const xs = [];
2584
+ for (let c = 0; c <= cols; c++) {
2585
+ xs.push(roundTo(x0 + (x1 - x0) * c / cols, 0.01));
2586
+ }
2587
+ return { xs, ys, weights, yFull: full / 100 };
2588
+ }
2589
+ function bodyMesh(b, rows) {
2590
+ const lines = [b.y1, ...rows.filter((y) => y > b.y0 && y < b.y1), b.y0];
2591
+ const ys = [lines[0]];
2592
+ for (let i = 1; i < lines.length; i++) {
2593
+ const [from, to] = [lines[i - 1], lines[i]];
2594
+ const n = Math.ceil((from - to) / BODY_MESH_PX);
2595
+ for (let k = 1; k < n; k++) ys.push(from - (from - to) * k / n);
2596
+ ys.push(to);
2597
+ }
2598
+ const cols = cellsFor(bw(b), BODY_MESH_PX, 2, 16);
2599
+ const vertices = [];
2600
+ const uvs = [];
2601
+ for (const y of ys) {
2602
+ const vy = roundTo((y - cy(b)) / bh(b), 1e-5);
2603
+ for (let c = 0; c <= cols; c++) {
2604
+ const vx = roundTo(c / cols - 0.5, 1e-5);
2605
+ vertices.push(vx, vy);
2606
+ uvs.push(roundTo(vx + 0.5, 1e-5), roundTo(0.5 - vy, 1e-5));
2607
+ }
2608
+ }
2609
+ const indices = [];
2610
+ for (let r = 0; r + 1 < ys.length; r++) {
2611
+ for (let c = 0; c < cols; c++) {
2612
+ const tl = r * (cols + 1) + c;
2613
+ const tr = tl + 1;
2614
+ const bl = tl + cols + 1;
2615
+ const br = bl + 1;
2616
+ indices.push(bl, br, tl, tl, br, tr);
2617
+ }
2618
+ }
2619
+ return { vertices, uvs, indices };
2620
+ }
2621
+ function buildBodyWarp({
2622
+ body,
2623
+ chin,
2624
+ hh,
2625
+ pivots
2626
+ }) {
2627
+ const b = boxOfLayer(body);
2628
+ const anchor = hipLine(body);
2629
+ const { xs, ys, weights } = bodyLattice(body, chin, hh, pivots, anchor);
2630
+ const hx = cx(b);
2631
+ const turn = (x, t) => t * BODY.slide * hh - Math.abs(t) * BODY.narrow * (x - hx);
2632
+ const roll = (x, y, deg) => {
2633
+ const c = Math.cos(deg * DEG) - 1;
2634
+ const s = Math.sin(deg * DEG);
2635
+ const [dx, dy] = [x - hx, y - anchor];
2636
+ return [c * dx - s * dy, s * dx + c * dy];
2637
+ };
2638
+ const warp = (parameter, values, field) => {
2639
+ const extreme = Math.max(...values.map(Math.abs));
2640
+ return {
2641
+ parameter,
2642
+ keyforms: values.map((value) => ({
2643
+ value,
2644
+ offsets: ys.flatMap(
2645
+ (y, r) => xs.flatMap((x) => {
2646
+ const [dx, dy] = field(x, y, value / extreme);
2647
+ return [
2648
+ roundTo(weights[r] * dx, 0.01),
2649
+ roundTo(weights[r] * dy, 0.01)
2650
+ ];
2651
+ })
2652
+ )
2653
+ }))
2654
+ };
2655
+ };
2656
+ const deformer = {
2657
+ kind: "warp",
2658
+ id: BODY_WARP_ID,
2659
+ grid: {
2660
+ cols: xs.length - 1,
2661
+ rows: ys.length - 1,
2662
+ points: ys.flatMap((y) => xs.flatMap((x) => [x, y]))
2663
+ },
2664
+ warps: [
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]),
2667
+ warp(
2668
+ import_format4.StandardParameter.BodyAngleZ,
2669
+ BODY_STOPS,
2670
+ (x, y, t) => roll(x, y, -t * BODY.rollDeg)
2671
+ ),
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])
2675
+ ]
2676
+ };
2677
+ refuseFold(body, deformer, anchor, [chin, ...pivots]);
2678
+ return { deformer, mesh: bodyMesh(b, ys) };
2679
+ }
2680
+ function refuseFold(body, d, anchor, pivots) {
2681
+ const { cols, rows, points } = d.grid;
2682
+ const warps = d.warps ?? [];
2683
+ const stride = cols + 1;
2684
+ for (let r = 0; r < rows; r++) {
2685
+ for (let c = 0; c < cols; c++) {
2686
+ const tl = r * stride + c;
2687
+ const [tr, bl, br] = [tl + 1, tl + stride, tl + stride + 1];
2688
+ const corners = [
2689
+ [bl, br, tl],
2690
+ [br, tr, bl],
2691
+ [tr, tl, br],
2692
+ [tl, bl, tr]
2693
+ ];
2694
+ for (const [p, q, s] of corners) {
2695
+ const fold = leastArea(points, warps, p, q, s);
2696
+ if (fold.least >= FOLD_MARGIN * fold.rest) continue;
2697
+ const lowest = roundTo(Math.min(...pivots.map((v) => v.y)), 0.01);
2698
+ const pose = warps.map((w, i) => `${w.parameter} ${w.keyforms[fold.stops[i]].value}`).join(", ");
2699
+ throw new LayerGeometryError(
2700
+ `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}`
2701
+ );
2702
+ }
2703
+ }
2704
+ }
2705
+ }
2706
+ function leastArea(points, warps, p, q, s) {
2707
+ const moves = warps.map(
2708
+ (w) => w.keyforms.map(({ offsets: o }) => [
2709
+ o[q * 2] - o[p * 2],
2710
+ o[q * 2 + 1] - o[p * 2 + 1],
2711
+ o[s * 2] - o[p * 2],
2712
+ o[s * 2 + 1] - o[p * 2 + 1]
2713
+ ])
2714
+ );
2715
+ const cross = (e) => e[0] * e[3] - e[1] * e[2];
2716
+ const edges = [
2717
+ points[q * 2] - points[p * 2],
2718
+ points[q * 2 + 1] - points[p * 2 + 1],
2719
+ points[s * 2] - points[p * 2],
2720
+ points[s * 2 + 1] - points[p * 2 + 1]
2721
+ ];
2722
+ let least = Infinity;
2723
+ let stops = [];
2724
+ const at = [];
2725
+ const walk = (w, ax, ay, bx, by) => {
2726
+ if (w === moves.length) {
2727
+ const area = ax * by - ay * bx;
2728
+ if (area < least) [least, stops] = [area, [...at]];
2729
+ return;
2730
+ }
2731
+ moves[w].forEach(([mx, my, nx, ny], k) => {
2732
+ at[w] = k;
2733
+ walk(w + 1, ax + mx, ay + my, bx + nx, by + ny);
2734
+ });
2735
+ };
2736
+ walk(0, edges[0], edges[1], edges[2], edges[3]);
2737
+ for (const m of moves) {
2738
+ let most = 0;
2739
+ for (let k = 1; k < m.length; k++) {
2740
+ most = Math.max(most, cross(m[k].map((v, i) => v - m[k - 1][i])));
2741
+ }
2742
+ least -= most / 4;
2743
+ }
2744
+ return { rest: cross(edges), least, stops };
2745
+ }
2746
+ function headOwnRoll(hasBody) {
2747
+ return hasBody ? ROLL_DEG - BODY.followRoll : ROLL_DEG;
2748
+ }
2749
+ function headOwnBreath(hasBody) {
2750
+ return hasBody ? AMPLITUDE.breathHead - AMPLITUDE.breathBody : AMPLITUDE.breathHead;
2751
+ }
2752
+
2753
+ // src/auto-rig/arms.ts
2754
+ var ELBOW_AT = 0.42;
2755
+ var CAP_ROWS = [0.1, 0.3];
2756
+ var CAP_SEGMENTS = 12;
2757
+ var ARM_RANGE = [-30, 150];
2758
+ var ARM_PARAMS = {
2759
+ arm_L: { arm: import_format5.StandardParameter.ArmLeft, elbow: import_format5.StandardParameter.ElbowLeft },
2760
+ arm_R: { arm: import_format5.StandardParameter.ArmRight, elbow: import_format5.StandardParameter.ElbowRight }
2761
+ };
2762
+ var ARM_IDS = {
2763
+ arm_L: {
2764
+ forearm: "forearm_L",
2765
+ armDeformer: "armDeformer_L",
2766
+ forearmDeformer: "forearmDeformer_L"
2767
+ },
2768
+ arm_R: {
2769
+ forearm: "forearm_R",
2770
+ armDeformer: "armDeformer_R",
2771
+ forearmDeformer: "forearmDeformer_R"
2772
+ }
2773
+ };
2774
+ function armGeometry(layer, bodyAxisX) {
2775
+ const { bbox, canvasW, canvasH, rowRuns } = layer;
2776
+ const b = boxOfLayer(layer);
2777
+ const span = (k) => {
2778
+ const runs = rowRuns?.[k] ?? [];
2779
+ if (runs.length === 0) return [bbox.x, bbox.x + bbox.w];
2780
+ let [s2, e2] = [runs[0], runs[1]];
2781
+ for (let i = 2; i < runs.length; i += 2) {
2782
+ if (runs[i + 1] - runs[i] > e2 - s2) [s2, e2] = [runs[i], runs[i + 1]];
2783
+ }
2784
+ return [s2, e2];
2785
+ };
2786
+ const centreX = (k) => {
2787
+ const [s2, e2] = span(k);
2788
+ return (s2 + e2) / 2 - canvasW / 2;
2789
+ };
2790
+ const rowY = (k) => canvasH / 2 - (bbox.y + k + 0.5);
2791
+ const painted = rowRuns?.flatMap((runs, k) => runs.length > 0 ? [k] : []) ?? [];
2792
+ const first = painted[0] ?? 0;
2793
+ const last = painted.at(-1) ?? bbox.h - 1;
2794
+ const lo = Math.ceil(CAP_ROWS[0] * bbox.h);
2795
+ const hi = Math.max(lo, Math.floor(CAP_ROWS[1] * bbox.h));
2796
+ const widths = [];
2797
+ for (let k = lo; k <= hi; k++) {
2798
+ const [s2, e2] = span(k);
2799
+ widths.push(e2 - s2);
2800
+ }
2801
+ widths.sort((p, q) => p - q);
2802
+ const ru = widths[widths.length >> 1] / 2;
2803
+ const ks = first + ru;
2804
+ const shoulder = {
2805
+ x: roundTo(centreX(Math.round(ks)), 0.01),
2806
+ y: roundTo(rowY(ks), 0.01)
2807
+ };
2808
+ const ke = Math.round(ks + ELBOW_AT * (last - ks));
2809
+ if (ke <= ks) {
2810
+ throw new LayerGeometryError(
2811
+ `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)`
2812
+ );
2813
+ }
2814
+ const elbow = { x: roundTo(centreX(ke), 0.01), y: roundTo(rowY(ke), 0.01) };
2815
+ const [s, e] = span(ke);
2816
+ const capRadius = Math.min(
2817
+ (e - s) / 2 + 1,
2818
+ elbow.x - b.x0,
2819
+ b.x1 - elbow.x,
2820
+ b.y1 - elbow.y
2821
+ );
2822
+ return {
2823
+ shoulder,
2824
+ shoulderRadius: ru,
2825
+ elbow,
2826
+ capRadius,
2827
+ side: shoulder.x > bodyAxisX ? 1 : -1
2828
+ };
2829
+ }
2830
+ function meshOf(vertices, indices) {
2831
+ const uvs = vertices.map(
2832
+ (v, i) => roundTo(i % 2 === 0 ? v + 0.5 : 0.5 - v, 1e-5)
2833
+ );
2834
+ return { vertices, uvs, indices };
2835
+ }
2836
+ function armParts(role, layer, g, order) {
2837
+ const b = boxOfLayer(layer);
2838
+ const lx = (x) => roundTo((x - cx(b)) / bw(b), 1e-5);
2839
+ const ly = (y) => roundTo((y - cy(b)) / bh(b), 1e-5);
2840
+ const seam = ly(g.elbow.y);
2841
+ const band = (top, bottom) => [
2842
+ -0.5,
2843
+ top,
2844
+ 0.5,
2845
+ top,
2846
+ -0.5,
2847
+ bottom,
2848
+ 0.5,
2849
+ bottom
2850
+ ];
2851
+ const quad = [2, 3, 0, 0, 3, 1];
2852
+ const cap = [lx(g.elbow.x), seam];
2853
+ const fan = [];
2854
+ for (let i = 0; i <= CAP_SEGMENTS; i++) {
2855
+ const a = Math.PI * i / CAP_SEGMENTS;
2856
+ cap.push(
2857
+ lx(g.elbow.x + g.capRadius * Math.cos(a)),
2858
+ ly(g.elbow.y + g.capRadius * Math.sin(a))
2859
+ );
2860
+ if (i > 0) fan.push(4, 4 + i, 5 + i);
2861
+ }
2862
+ const part = (id, deformer, at, mesh) => ({
2863
+ id,
2864
+ color: [1, 1, 1, 1],
2865
+ width: bw(b),
2866
+ height: bh(b),
2867
+ transform: { x: cx(b), y: cy(b) },
2868
+ order: at,
2869
+ deformer,
2870
+ mesh
2871
+ });
2872
+ const ids = ARM_IDS[role];
2873
+ return [
2874
+ part(role, ids.armDeformer, order, meshOf(band(0.5, seam), quad)),
2875
+ part(
2876
+ ids.forearm,
2877
+ ids.forearmDeformer,
2878
+ order + 1,
2879
+ meshOf([...band(seam, -0.5), ...cap], [...quad, ...fan])
2880
+ )
2881
+ ];
2882
+ }
2883
+ function armDeformers(role, g) {
2884
+ const ids = ARM_IDS[role];
2885
+ const params = ARM_PARAMS[role];
2886
+ const rotate = (parameter) => ({
2887
+ parameter,
2888
+ channel: "rotate",
2889
+ from: g.side * ARM_RANGE[0],
2890
+ to: g.side * ARM_RANGE[1]
2891
+ });
2892
+ return [
2893
+ {
2894
+ id: ids.armDeformer,
2895
+ parent: BODY_WARP_ID,
2896
+ pivot: { ...g.shoulder },
2897
+ bindings: [rotate(params.arm)]
2898
+ },
2899
+ {
2900
+ id: ids.forearmDeformer,
2901
+ parent: ids.armDeformer,
2902
+ pivot: { ...g.elbow },
2903
+ bindings: [rotate(params.elbow)]
2904
+ }
2905
+ ];
2906
+ }
2907
+
2908
+ // src/auto-rig/roles.ts
2909
+ var ROLE_TABLE = [
2910
+ { role: "hair_back", family: "hair_back" },
2911
+ { role: "body", family: "body" },
2912
+ { role: "arm_L", family: "arm", parts: ["arm_L", "forearm_L"] },
2913
+ { role: "arm_R", family: "arm", parts: ["arm_R", "forearm_R"] },
2914
+ { role: "face", family: "face", required: true },
2915
+ { role: "blush_L", family: "face" },
2916
+ { role: "blush_R", family: "face" },
2917
+ { role: "nose", family: "nose" },
2918
+ { role: "mouth", family: "mouth", required: true },
2919
+ { role: "mouth_open", family: "mouth" },
2920
+ { role: "eye_L", family: "eye_L", required: true },
2921
+ { role: "eye_R", family: "eye_R", required: true },
2922
+ { role: "iris_L", family: "eye_L" },
2923
+ { role: "iris_R", family: "eye_R" },
2924
+ { role: "pupil_L", family: "eye_L" },
2925
+ { role: "pupil_R", family: "eye_R" },
2926
+ { role: "highlight_L", family: "eye_L" },
2927
+ { role: "highlight_R", family: "eye_R" },
2928
+ { role: "lash_lower_L", family: "eye_L" },
2929
+ { role: "lash_lower_R", family: "eye_R" },
2930
+ { role: "lash_L", family: "eye_L" },
2931
+ { role: "lash_R", family: "eye_R" },
2932
+ { role: "brow_L", family: "brow_L" },
2933
+ { role: "brow_R", family: "brow_R" },
2934
+ { role: "hair_front", family: "hair_front" }
2935
+ ];
2936
+ var REQUIRED_ROLES = ROLE_TABLE.filter(
2937
+ (r) => r.required
2938
+ ).map((r) => r.role);
2939
+ var SPEC_BY_ROLE = new Map(ROLE_TABLE.map((r, i) => [r.role, { ...r, i }]));
2940
+ function roleSpec(role) {
2941
+ const spec = SPEC_BY_ROLE.get(role);
2942
+ if (spec === void 0) throw new Error(`auto-rig: unknown role "${role}"`);
2943
+ return spec;
2944
+ }
2945
+ function partIdsOfRole(role) {
2946
+ return roleSpec(role).parts ?? [role];
2947
+ }
2948
+ var ROLE_BY_PART = new Map(
2949
+ ROLE_TABLE.flatMap((r) => (r.parts ?? []).map((p) => [p, r.role]))
2950
+ );
2951
+ function roleOfPart(partId) {
2952
+ return ROLE_BY_PART.get(partId) ?? partId;
2953
+ }
2954
+ var ALIASES = {
2955
+ eyebrow_L: "brow_L",
2956
+ eyebrow_R: "brow_R",
2957
+ eye_white_L: "eye_L",
2958
+ eye_white_R: "eye_R"
2959
+ };
2960
+ function roleOf(fileName) {
2961
+ const role = fileName.replace(/\.[^.]+$/, "").toLowerCase().replace(/[-\s]+/g, "_").replace(/_([lr])$/, (_, s) => `_${s.toUpperCase()}`);
2962
+ return ALIASES[role] ?? role;
2963
+ }
2964
+ function checkArmsHaveBody(roles) {
2965
+ const has = new Set(roles);
2966
+ if (has.has("body")) return;
2967
+ const arm = ROLE_TABLE.find((r) => r.family === "arm" && has.has(r.role));
2968
+ if (arm !== void 0) {
2969
+ throw new Error(
2970
+ `auto-rig: ${arm.role} needs a body layer (an arm hangs from the body's shoulder)`
2971
+ );
2972
+ }
2973
+ }
2974
+ function parseLayerRoles(fileNames) {
2975
+ const seen = /* @__PURE__ */ new Map();
2976
+ const out = [];
2977
+ for (const fileName of fileNames) {
2978
+ const role = roleOf(fileName);
2979
+ if (!SPEC_BY_ROLE.has(role)) {
2980
+ throw new Error(
2981
+ `auto-rig: "${fileName}" names no known role (expected one of ${ROLE_TABLE.map((r) => r.role).join(", ")})`
2982
+ );
2983
+ }
2984
+ const prior = seen.get(role);
2985
+ if (prior !== void 0) {
2986
+ throw new Error(
2987
+ `auto-rig: role "${role}" is named twice ("${prior}" and "${fileName}")`
2988
+ );
2989
+ }
2990
+ seen.set(role, fileName);
2991
+ out.push({ role, fileName });
2992
+ }
2993
+ const missing = REQUIRED_ROLES.filter((r) => !seen.has(r));
2994
+ if (missing.length > 0) {
2995
+ throw new Error(
2996
+ `auto-rig: missing required role${missing.length > 1 ? "s" : ""} ${missing.join(", ")}`
2997
+ );
2998
+ }
2999
+ checkArmsHaveBody(seen.keys());
3000
+ return out;
3001
+ }
3002
+
3003
+ // src/auto-rig/generate.ts
3004
+ var import_format8 = require("@ikijs/format");
3005
+
3006
+ // src/auto-rig/animations.ts
3007
+ var import_format6 = require("@ikijs/format");
3008
+ function browsY(y) {
3009
+ return [
3010
+ { parameter: import_format6.StandardParameter.BrowLeftY, value: y, blend: "add" },
3011
+ { parameter: import_format6.StandardParameter.BrowRightY, value: y, blend: "add" }
3012
+ ];
3013
+ }
3014
+ function browsTilt(innerDown) {
3015
+ return [
3016
+ { parameter: import_format6.StandardParameter.BrowLeftAngle, value: innerDown, blend: "add" },
3017
+ { parameter: import_format6.StandardParameter.BrowRightAngle, value: -innerDown, blend: "add" }
3018
+ ];
3019
+ }
3020
+ function cheek(value) {
3021
+ return { parameter: import_format6.StandardParameter.Cheek, value, blend: "add" };
3022
+ }
3023
+ var BROWS = [import_format6.StandardParameter.BrowLeftY, import_format6.StandardParameter.BrowRightY, import_format6.StandardParameter.BrowLeftAngle, import_format6.StandardParameter.BrowRightAngle];
3024
+ var EXPRESSIONS = [
3025
+ {
3026
+ id: "smile",
3027
+ description: "Smiling and pleased: warm, friendly, content. For greetings, thanks, agreement and gentle happiness.",
3028
+ parameters: [
3029
+ { parameter: import_format6.StandardParameter.MouthForm, value: 1, blend: "add" },
3030
+ ...browsY(0.25),
3031
+ cheek(0.4)
3032
+ ]
3033
+ },
3034
+ {
3035
+ id: "laugh",
3036
+ description: "Laughing, eyes shut and mouth open: delighted, amused. For jokes, playfulness and big joy.",
3037
+ parameters: [
3038
+ { parameter: import_format6.StandardParameter.EyeOpenLeft, value: 0, blend: "multiply" },
3039
+ { parameter: import_format6.StandardParameter.EyeOpenRight, value: 0, blend: "multiply" },
3040
+ { parameter: import_format6.StandardParameter.MouthForm, value: 1, blend: "add" },
3041
+ { parameter: import_format6.StandardParameter.MouthOpen, value: 0.8, blend: "overwrite" },
3042
+ ...browsY(0.4),
3043
+ cheek(0.65)
3044
+ ]
3045
+ },
3046
+ {
3047
+ id: "angry",
3048
+ description: "Angry or annoyed, frowning. For irritation, frustration, indignation or scolding.",
3049
+ needs: BROWS,
3050
+ parameters: [
3051
+ ...browsY(-0.5),
3052
+ ...browsTilt(0.8),
3053
+ { parameter: import_format6.StandardParameter.MouthForm, value: -0.9, blend: "add" }
3054
+ ]
3055
+ },
3056
+ {
3057
+ id: "sad",
3058
+ description: "Sad or disappointed, downcast. For sorrow, regret, apology or sympathy.",
3059
+ needs: BROWS,
3060
+ parameters: [
3061
+ ...browsY(0.35),
3062
+ ...browsTilt(-0.8),
3063
+ { parameter: import_format6.StandardParameter.MouthForm, value: -0.8, blend: "add" }
3064
+ ]
3065
+ },
3066
+ {
3067
+ id: "surprised",
3068
+ description: "Surprised, mouth dropped open: startled, amazed. For shock, sudden news or disbelief.",
3069
+ parameters: [
3070
+ ...browsY(0.8),
3071
+ { parameter: import_format6.StandardParameter.MouthOpen, value: 0.9, blend: "overwrite" }
3072
+ ]
3073
+ },
3074
+ {
3075
+ id: "shy",
3076
+ description: "Shy or embarrassed: bashful, flustered. For being praised, teased or caught off guard.",
3077
+ needs: [import_format6.StandardParameter.Cheek],
3078
+ parameters: [
3079
+ cheek(1),
3080
+ // Gaze down.
3081
+ { parameter: import_format6.StandardParameter.EyeballY, value: -0.65, blend: "add" },
3082
+ { parameter: import_format6.StandardParameter.MouthForm, value: 0.4, blend: "add" },
3083
+ ...browsTilt(-0.25)
3084
+ ]
3085
+ }
3086
+ ];
3087
+ function defaultExpressions(declared) {
3088
+ return EXPRESSIONS.filter(
3089
+ (e) => e.needs === void 0 || e.needs.some((id) => declared.has(id))
3090
+ ).map(({ needs: _needs, ...e }) => ({
3091
+ ...e,
3092
+ parameters: e.parameters.filter((t) => declared.has(t.parameter))
3093
+ }));
3094
+ }
3095
+ var DEFAULT_MOTIONS = {
3096
+ Nod: [
3097
+ {
3098
+ description: "Nods yes: agreement, acknowledgement, understanding.",
3099
+ duration: 1,
3100
+ curves: [
3101
+ {
3102
+ parameter: import_format6.StandardParameter.AngleY,
3103
+ keys: [
3104
+ [0, 0],
3105
+ [0.3, -18],
3106
+ [0.6, 4],
3107
+ [0.8, -1],
3108
+ [1, 0]
3109
+ ]
3110
+ }
3111
+ ]
3112
+ }
3113
+ ],
3114
+ Shake: [
3115
+ {
3116
+ description: "Shakes the head no: disagreement, refusal, disbelief.",
3117
+ duration: 1.2,
3118
+ // The first swing peaks at 0.2 s, which the default fade would still
3119
+ // be damping.
3120
+ fadeIn: 0.15,
3121
+ curves: [
3122
+ {
3123
+ parameter: import_format6.StandardParameter.AngleX,
3124
+ keys: [
3125
+ [0, 0],
3126
+ [0.2, -18],
3127
+ [0.45, 18],
3128
+ [0.7, -16],
3129
+ [0.95, 10],
3130
+ [1.2, 0]
3131
+ ]
3132
+ }
3133
+ ]
3134
+ }
3135
+ ],
3136
+ Tilt: [
3137
+ {
3138
+ description: "Tilts the head to one side and back: curiosity, puzzlement, a question.",
3139
+ duration: 1.4,
3140
+ curves: [
3141
+ {
3142
+ parameter: import_format6.StandardParameter.AngleZ,
3143
+ keys: [
3144
+ [0, 0],
3145
+ [0.35, 20],
3146
+ [1, 20],
3147
+ [1.4, 0]
3148
+ ]
3149
+ }
3150
+ ]
3151
+ }
3152
+ ]
3153
+ };
3154
+
3155
+ // src/auto-rig/checks.ts
3156
+ var finite = (v) => typeof v === "number" && Number.isFinite(v);
3157
+ var isObject = (v) => typeof v === "object" && v !== null;
3158
+ var MIN_FACE_PX = 24;
3159
+ function checkLayers(layers, canvas) {
3160
+ if (!Array.isArray(layers) || layers.length === 0) {
3161
+ throw new Error(
3162
+ "auto-rig: generateIkiFromLayerSet: layers must be a non-empty array"
3163
+ );
3164
+ }
3165
+ if (!(finite(canvas?.width) && canvas.width > 0)) {
3166
+ throw new Error("auto-rig: canvas.width must be a positive number");
3167
+ }
3168
+ if (!(finite(canvas?.height) && canvas.height > 0)) {
3169
+ throw new Error("auto-rig: canvas.height must be a positive number");
3170
+ }
3171
+ const seen = /* @__PURE__ */ new Set();
3172
+ for (const l of layers) {
3173
+ roleSpec(l.role);
3174
+ if (seen.has(l.role)) {
3175
+ throw new Error(`auto-rig: role "${l.role}" appears twice`);
3176
+ }
3177
+ seen.add(l.role);
3178
+ const at = `auto-rig: layer "${l.fileName}"`;
3179
+ const { x, y, w, h } = l.bbox ?? {};
3180
+ if (![x, y, w, h].every(finite) || w <= 0 || h <= 0 || x < 0 || y < 0 || x + w > canvas.width || y + h > canvas.height) {
3181
+ throw new Error(
3182
+ `${at} has a bbox outside its ${canvas.width}x${canvas.height} canvas`
3183
+ );
3184
+ }
3185
+ if (l.canvasW !== canvas.width || l.canvasH !== canvas.height) {
3186
+ throw new Error(
3187
+ `${at} was measured on a ${l.canvasW}x${l.canvasH} canvas, not ${canvas.width}x${canvas.height}`
3188
+ );
3189
+ }
3190
+ if (l.cropW !== w || l.cropH !== h) {
3191
+ throw new Error(
3192
+ `${at}: crop ${l.cropW}x${l.cropH} is not its bbox ${w}x${h}`
3193
+ );
3194
+ }
3195
+ if (l.role === "face" && (w < MIN_FACE_PX || h < MIN_FACE_PX)) {
3196
+ throw new Error(
3197
+ `${at}: the face is ${w}x${h} px; the face plate needs at least ${MIN_FACE_PX} px each way to be meshed`
2479
3198
  );
2480
3199
  }
2481
3200
  if (l.rowHalfWidths !== void 0) {
@@ -2536,6 +3255,7 @@ function checkLayers(layers, canvas) {
2536
3255
  if (missing.length > 0) {
2537
3256
  throw new Error(`auto-rig: missing required role(s) ${missing.join(", ")}`);
2538
3257
  }
3258
+ checkArmsHaveBody(seen);
2539
3259
  }
2540
3260
  function checkHeadEdges(edges, byRole) {
2541
3261
  if (edges === void 0) return;
@@ -2616,131 +3336,45 @@ function checkStrandEdges(strand, irises, hair) {
2616
3336
  for (const [k, x] of [
2617
3337
  ["irisOuter", outer],
2618
3338
  ["irisInner", inner]
2619
- ]) {
2620
- if (x < iris.x0 || x > iris.x1) {
2621
- throw new TurnTargetError(`${at}.${k} ${x} is off its iris's crop`);
2622
- }
2623
- }
2624
- if (sign * (outer - inner) <= 0) {
2625
- throw new TurnTargetError(
2626
- `${at}.irisOuter ${outer} is not strictly outward of irisInner ${inner}`
2627
- );
2628
- }
2629
- for (const [k, x] of [
2630
- ["runOuter", runOuter],
2631
- ["runFace", runFace]
2632
- ]) {
2633
- if (x !== null && (x < hair.x0 || x > hair.x1)) {
2634
- throw new TurnTargetError(`${at}.${k} ${x} is off hair_front's crop`);
2635
- }
2636
- }
2637
- if (sign * (runOuter - cx(iris)) <= 0) {
2638
- throw new TurnTargetError(
2639
- `${at}.runOuter ${runOuter} is not strictly outward of its iris's centre`
2640
- );
2641
- }
2642
- if (runFace !== null && sign * (runOuter - runFace) <= 0) {
2643
- throw new TurnTargetError(
2644
- `${at}.runOuter ${runOuter} is not strictly outward of runFace ${runFace}`
2645
- );
2646
- }
2647
- if (runFace !== null && sign * (runFace - cx(other)) <= 0) {
2648
- throw new TurnTargetError(
2649
- `${at}.runFace ${runFace} reaches the other iris's centre; a run that does is a fringe, runFace null`
2650
- );
2651
- }
2652
- }
2653
- }
2654
-
2655
- // src/auto-rig/context.ts
2656
- var import_format4 = require("@ikijs/format");
2657
-
2658
- // src/auto-rig/grid.ts
2659
- var X_STOPS = [-30, 0, 30];
2660
- var Y_STOPS = [-30, 0, 30];
2661
- function lattice(box, cell, min, max) {
2662
- const cols = cellsFor(box.x1 - box.x0, cell, min, max);
2663
- const rows = cellsFor(box.y1 - box.y0, cell, min, max);
2664
- const points = [];
2665
- for (let r = 0; r <= rows; r++) {
2666
- const y = box.y1 - (box.y1 - box.y0) * r / rows;
2667
- for (let c = 0; c <= cols; c++) {
2668
- points.push(
2669
- roundTo(box.x0 + (box.x1 - box.x0) * c / cols, 0.01),
2670
- roundTo(y, 0.01)
2671
- );
2672
- }
2673
- }
2674
- return { box, cols, rows, points };
2675
- }
2676
- function bake(l, field) {
2677
- const out = [];
2678
- for (const ay of Y_STOPS) {
2679
- for (const ax of X_STOPS) {
2680
- const offsets = [];
2681
- for (let k = 0; k < l.points.length; k += 2) {
2682
- if (ax === 0 && ay === 0) {
2683
- offsets.push(0, 0);
2684
- continue;
2685
- }
2686
- const [dx, dy] = field(l.points[k], l.points[k + 1], ax, ay);
2687
- offsets.push(roundTo(dx, 0.1), roundTo(dy, 0.1));
3339
+ ]) {
3340
+ if (x < iris.x0 || x > iris.x1) {
3341
+ throw new TurnTargetError(`${at}.${k} ${x} is off its iris's crop`);
2688
3342
  }
2689
- out.push(offsets);
2690
3343
  }
2691
- }
2692
- return out;
2693
- }
2694
- function keyformAt(keyforms, ax) {
2695
- const j = Y_STOPS.indexOf(0);
2696
- const row = X_STOPS.map((_, i) => keyforms[j * X_STOPS.length + i]);
2697
- return blendStops(X_STOPS, row, ax);
2698
- }
2699
- function blendStops(values, offsets, v) {
2700
- const x = clamp(v, values[0], values[values.length - 1]);
2701
- let i = 0;
2702
- while (i < values.length - 2 && x > values[i + 1]) i++;
2703
- const t = (x - values[i]) / (values[i + 1] - values[i]);
2704
- return offsets[i].map((o, k) => o + (offsets[i + 1][k] - o) * t);
2705
- }
2706
- function land(l, offsets, x, y) {
2707
- const { cols, rows, points } = l;
2708
- const stride = cols + 1;
2709
- let col = cols - 1;
2710
- for (let c = 0; c < cols; c++) {
2711
- if (x < points[(c + 1) * 2]) {
2712
- col = c;
2713
- break;
3344
+ if (sign * (outer - inner) <= 0) {
3345
+ throw new TurnTargetError(
3346
+ `${at}.irisOuter ${outer} is not strictly outward of irisInner ${inner}`
3347
+ );
2714
3348
  }
2715
- }
2716
- let row = rows - 1;
2717
- for (let r = 0; r < rows; r++) {
2718
- if (y > points[(r + 1) * stride * 2 + 1]) {
2719
- row = r;
2720
- break;
3349
+ for (const [k, x] of [
3350
+ ["runOuter", runOuter],
3351
+ ["runFace", runFace]
3352
+ ]) {
3353
+ if (x !== null && (x < hair.x0 || x > hair.x1)) {
3354
+ throw new TurnTargetError(`${at}.${k} ${x} is off hair_front's crop`);
3355
+ }
3356
+ }
3357
+ if (sign * (runOuter - cx(iris)) <= 0) {
3358
+ throw new TurnTargetError(
3359
+ `${at}.runOuter ${runOuter} is not strictly outward of its iris's centre`
3360
+ );
3361
+ }
3362
+ if (runFace !== null && sign * (runOuter - runFace) <= 0) {
3363
+ throw new TurnTargetError(
3364
+ `${at}.runOuter ${runOuter} is not strictly outward of runFace ${runFace}`
3365
+ );
3366
+ }
3367
+ if (runFace !== null && sign * (runFace - cx(other)) <= 0) {
3368
+ throw new TurnTargetError(
3369
+ `${at}.runFace ${runFace} reaches the other iris's centre; a run that does is a fringe, runFace null`
3370
+ );
2721
3371
  }
2722
3372
  }
2723
- const x0 = points[col * 2];
2724
- const x1 = points[(col + 1) * 2];
2725
- const yT = points[row * stride * 2 + 1];
2726
- const yB = points[(row + 1) * stride * 2 + 1];
2727
- const s = clamp((x - x0) / (x1 - x0), 0, 1);
2728
- const t = clamp((yT - y) / (yT - yB), 0, 1);
2729
- const at = (r, c) => {
2730
- const k = (r * stride + c) * 2;
2731
- return [points[k] + offsets[k], points[k + 1] + offsets[k + 1]];
2732
- };
2733
- const p00 = at(row, col);
2734
- const p10 = at(row, col + 1);
2735
- const p01 = at(row + 1, col);
2736
- const p11 = at(row + 1, col + 1);
2737
- const topX = p00[0] + (p10[0] - p00[0]) * s;
2738
- const topY = p00[1] + (p10[1] - p00[1]) * s;
2739
- const botX = p01[0] + (p11[0] - p01[0]) * s;
2740
- const botY = p01[1] + (p11[1] - p01[1]) * s;
2741
- return [topX + (botX - topX) * t, topY + (botY - topY) * t];
2742
3373
  }
2743
3374
 
3375
+ // src/auto-rig/context.ts
3376
+ var import_format7 = require("@ikijs/format");
3377
+
2744
3378
  // src/auto-rig/head.ts
2745
3379
  function outerEdge(runs, axisX, side) {
2746
3380
  if (runs.length === 0) return void 0;
@@ -3770,6 +4404,7 @@ function familyField(m, family) {
3770
4404
  case "hair_back":
3771
4405
  return hairField(m, m.hairBack, NOD.hairBack);
3772
4406
  case "body":
4407
+ case "arm":
3773
4408
  return () => [0, 0];
3774
4409
  }
3775
4410
  }
@@ -3869,7 +4504,7 @@ function renderedLander(turn, parts, grids, from = /* @__PURE__ */ new Map()) {
3869
4504
  const family = roleSpec(role).family;
3870
4505
  const part = parts.find((p) => p.id === role);
3871
4506
  const g = grids.get(family);
3872
- const warp = part?.warps?.find((w) => w.parameter === import_format4.StandardParameter.AngleX);
4507
+ const warp = part?.warps?.find((w) => w.parameter === import_format7.StandardParameter.AngleX);
3873
4508
  if (part === void 0 || part.mesh === void 0 || !g && !warp) {
3874
4509
  return x + field(role)(x, y, ax, 0)[0];
3875
4510
  }
@@ -4578,177 +5213,6 @@ function buildTurn(frame, landmarks, q) {
4578
5213
  };
4579
5214
  }
4580
5215
 
4581
- // src/auto-rig/drivers.ts
4582
- var import_format5 = require("@ikijs/format");
4583
- var SWAY_RANGE = 20;
4584
- var SWAY_CURVE = 1.4;
4585
- var HAIR_HANG = 0.35;
4586
- var Z_STOPS = [-30, 0, 30];
4587
- function localWarp(parameter, mesh, box, values, f) {
4588
- const pts = meshPoints(mesh, box);
4589
- const w = bw(box);
4590
- const h = bh(box);
4591
- return {
4592
- parameter,
4593
- keyforms: values.map((value) => ({
4594
- value,
4595
- offsets: pts.flatMap((p, i) => {
4596
- const [dx, dy] = f(p, value, i);
4597
- return [roundTo(dx / w, 1e-4), roundTo(dy / h, 1e-4)];
4598
- })
4599
- }))
4600
- };
4601
- }
4602
- var rollOf = (v) => -v / 30 * ROLL_DEG * DEG;
4603
- function motionReach(warps, mesh, box, extra = [0, 0]) {
4604
- const out = {
4605
- left: extra[0],
4606
- right: extra[0],
4607
- bottom: extra[1],
4608
- top: extra[1]
4609
- };
4610
- if (mesh === void 0) return out;
4611
- const pts = meshPoints(mesh, box);
4612
- for (const warp of warps ?? []) {
4613
- let l = 0;
4614
- let r = 0;
4615
- let b = 0;
4616
- let t = 0;
4617
- for (const k of warp.keyforms) {
4618
- pts.forEach(([x, y], i) => {
4619
- const px = x + k.offsets[2 * i] * bw(box);
4620
- const py = y + k.offsets[2 * i + 1] * bh(box);
4621
- l = Math.max(l, box.x0 - px);
4622
- r = Math.max(r, px - box.x1);
4623
- b = Math.max(b, box.y0 - py);
4624
- t = Math.max(t, py - box.y1);
4625
- });
4626
- }
4627
- out.left += l;
4628
- out.right += r;
4629
- out.bottom += b;
4630
- out.top += t;
4631
- }
4632
- return out;
4633
- }
4634
- function blinkFold(param, mesh, box, travel, eye = box) {
4635
- const yc = eye.y1 - travel * bh(eye);
4636
- return localWarp(
4637
- param,
4638
- mesh,
4639
- box,
4640
- [0, 1],
4641
- ([, y], v) => v === 1 ? [0, 0] : [0, yc - y]
4642
- );
4643
- }
4644
- function lashFold(param, mesh, lash, eye, travel) {
4645
- const yc = eye.y1 - travel * bh(eye) - 0.04 * bh(eye);
4646
- const c = cx(lash);
4647
- const half = bw(lash) / 2;
4648
- const sag = 0.3 * bh(lash);
4649
- return localWarp(param, mesh, lash, [0, 1], ([x, y], v) => {
4650
- if (v === 1) return [0, 0];
4651
- const u = (x - c) / half;
4652
- return [0, yc + (y - lash.y0) * 0.5 - sag * (1 - u * u) - y];
4653
- });
4654
- }
4655
- function gazeBindings(irisW, share = 1) {
4656
- const gx = AMPLITUDE.gazeX * irisW * share;
4657
- const gy = AMPLITUDE.gazeY * irisW * share;
4658
- return [
4659
- {
4660
- parameter: import_format5.StandardParameter.EyeballX,
4661
- channel: "translateX",
4662
- from: -round1(gx),
4663
- to: round1(gx)
4664
- },
4665
- {
4666
- parameter: import_format5.StandardParameter.EyeballY,
4667
- channel: "translateY",
4668
- from: -round1(gy),
4669
- to: round1(gy)
4670
- }
4671
- ];
4672
- }
4673
- function gazeReach(irisW, share = 1) {
4674
- return [AMPLITUDE.gazeX * irisW * share, AMPLITUDE.gazeY * irisW * share];
4675
- }
4676
- function browReach(hh) {
4677
- return AMPLITUDE.brow * hh;
4678
- }
4679
- function browBindings(side, reach) {
4680
- const y = side === "L" ? import_format5.StandardParameter.BrowLeftY : import_format5.StandardParameter.BrowRightY;
4681
- const a = side === "L" ? import_format5.StandardParameter.BrowLeftAngle : import_format5.StandardParameter.BrowRightAngle;
4682
- return [
4683
- {
4684
- parameter: y,
4685
- channel: "translateY",
4686
- from: -round1(reach),
4687
- to: round1(reach)
4688
- },
4689
- { parameter: a, channel: "rotate", from: -BROW_TILT, to: BROW_TILT }
4690
- ];
4691
- }
4692
- var BROW_TILT = 15;
4693
- function mouthForm(mesh, box) {
4694
- const a = 0.3 * bh(box);
4695
- const c = cx(box);
4696
- const half = bw(box) / 2;
4697
- return localWarp(import_format5.StandardParameter.MouthForm, mesh, box, [-1, 0, 1], ([x], v) => {
4698
- if (v === 0) return [0, 0];
4699
- const u = (x - c) / half;
4700
- const widen = v > 0 ? 0.05 : 0.03;
4701
- return [v * widen * (x - c), v * a * (u * u - 0.3)];
4702
- });
4703
- }
4704
- function mouthOpenGrow(mesh, box) {
4705
- const c = cx(box);
4706
- return localWarp(
4707
- import_format5.StandardParameter.MouthOpen,
4708
- mesh,
4709
- box,
4710
- [0, 1],
4711
- ([x, y], v) => v === 1 ? [(AMPLITUDE.mouthOpenWidth - 1) * (x - c), 0] : [0, box.y1 + (y - box.y1) * 0.2 - y]
4712
- );
4713
- }
4714
- function mouthWiden(mesh, box) {
4715
- const c = cx(box);
4716
- return localWarp(import_format5.StandardParameter.MouthOpen, mesh, box, [0, 1], ([x], v) => [
4717
- v * (AMPLITUDE.mouthOpenWidth - 1) * (x - c),
4718
- 0
4719
- ]);
4720
- }
4721
- function hairSway(param, mesh, box, amp) {
4722
- return localWarp(
4723
- param,
4724
- mesh,
4725
- box,
4726
- [-SWAY_RANGE, 0, SWAY_RANGE],
4727
- ([, y], v) => {
4728
- const t = Math.max(0, (box.y1 - y) / bh(box));
4729
- return [v / SWAY_RANGE * amp * t ** SWAY_CURVE, 0];
4730
- }
4731
- );
4732
- }
4733
- function tiltHang(mesh, box, pivot, weight) {
4734
- return localWarp(import_format5.StandardParameter.AngleZ, mesh, box, Z_STOPS, ([x, y], v, i) => {
4735
- const g = weight(y, i);
4736
- if (g === 0 || v === 0) return [0, 0];
4737
- const a = -g * rollOf(v);
4738
- const px = x - pivot[0];
4739
- const py = y - pivot[1];
4740
- return [
4741
- pivot[0] + px * Math.cos(a) - py * Math.sin(a) - x,
4742
- pivot[1] + px * Math.sin(a) + py * Math.cos(a) - y
4743
- ];
4744
- });
4745
- }
4746
- function hairHangWeight(pivotY, bottom) {
4747
- if (bottom >= pivotY) return () => 0;
4748
- return (y) => HAIR_HANG * smoothstep(pivotY, bottom, y);
4749
- }
4750
- var round1 = (v) => roundTo(v, 0.1);
4751
-
4752
5216
  // src/auto-rig/generate.ts
4753
5217
  var MESH_CELLS = {
4754
5218
  face: { px: 28, min: 4, max: 24 },
@@ -4775,6 +5239,7 @@ var WARP_ID = {
4775
5239
  mouth: "mouthWarp"
4776
5240
  };
4777
5241
  var isGridFamily = (f) => f in WARP_ID;
5242
+ var isArm = (spec) => spec.family === "arm";
4778
5243
  var GRID_PAD = 6;
4779
5244
  function generateIkiFromLayerSet(layers, canvas, options = {}) {
4780
5245
  checkLayers(layers, canvas);
@@ -4823,6 +5288,18 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4823
5288
  chinSlide: chinSlide(fit.model)
4824
5289
  });
4825
5290
  const turn = { ...fit.model, frame };
5291
+ const arms = /* @__PURE__ */ new Map();
5292
+ for (const spec of ROLE_TABLE) {
5293
+ const layer = byRole.get(spec.role);
5294
+ if (layer === void 0 || !isArm(spec)) continue;
5295
+ arms.set(spec.role, armGeometry(layer, cx(box("body"))));
5296
+ }
5297
+ const bodyWarp = has("body") ? buildBodyWarp({
5298
+ body: byRole.get("body"),
5299
+ chin: { x: frame.axisX, y: frame.chinY },
5300
+ hh: frame.hh,
5301
+ pivots: [...arms.values()].map((g) => g.shoulder)
5302
+ }) : void 0;
4826
5303
  const parts = [];
4827
5304
  const reach = /* @__PURE__ */ new Map();
4828
5305
  let regionOf = [];
@@ -4830,6 +5307,12 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4830
5307
  for (const spec of ROLE_TABLE) {
4831
5308
  const layer = byRole.get(spec.role);
4832
5309
  if (layer === void 0) continue;
5310
+ if (isArm(spec)) {
5311
+ parts.push(
5312
+ ...armParts(spec.role, layer, arms.get(spec.role), parts.length)
5313
+ );
5314
+ continue;
5315
+ }
4833
5316
  const built = buildPart(
4834
5317
  spec,
4835
5318
  boxOfLayer(layer),
@@ -4848,7 +5331,9 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4848
5331
  }
4849
5332
  const grids = /* @__PURE__ */ new Map();
4850
5333
  for (const family of Object.keys(WARP_ID)) {
4851
- const members = parts.filter((p) => roleSpec(p.id).family === family);
5334
+ const members = parts.filter(
5335
+ (p) => roleSpec(roleOfPart(p.id)).family === family
5336
+ );
4852
5337
  if (members.length === 0) continue;
4853
5338
  const area = unionBoxes(
4854
5339
  members.map((p) => {
@@ -4870,13 +5355,19 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4870
5355
  });
4871
5356
  }
4872
5357
  for (const part of parts) {
4873
- const family = roleSpec(part.id).family;
5358
+ const family = roleSpec(roleOfPart(part.id)).family;
4874
5359
  if (isGridFamily(family)) {
4875
5360
  part.deformer = WARP_ID[family];
4876
5361
  continue;
4877
5362
  }
4878
- part.deformer = family === "body" ? "bodyDeformer" : "headDeformer";
4879
- if (family === "body" || part.mesh === void 0) continue;
5363
+ if (family === "body") {
5364
+ part.deformer = BODY_WARP_ID;
5365
+ part.mesh = bodyWarp.mesh;
5366
+ continue;
5367
+ }
5368
+ if (family === "arm") continue;
5369
+ part.deformer = "headDeformer";
5370
+ if (part.mesh === void 0) continue;
4880
5371
  const own = familyField(turn, family);
4881
5372
  const fieldOf = part.id === "face" ? plateFields(turn, regionOf, own) : () => own;
4882
5373
  part.warps = [
@@ -4886,17 +5377,17 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4886
5377
  }
4887
5378
  const parameters = declareParameters(new Set(byRole.keys()));
4888
5379
  const model = {
4889
- version: import_format6.IKI_FORMAT_VERSION,
5380
+ version: import_format8.IKI_FORMAT_VERSION,
4890
5381
  name: "auto-rigged",
4891
5382
  canvas: { width: canvas.width, height: canvas.height },
4892
5383
  parameters,
4893
5384
  parts,
4894
- deformers: deformers(frame, grids, has("body") ? box("body") : void 0),
5385
+ deformers: deformers(frame, grids, bodyWarp?.deformer, arms),
4895
5386
  ...has("hair_front") ? { physics: hairPhysics() } : {},
4896
5387
  expressions: defaultExpressions(new Set(parameters.map((p) => p.id))),
4897
5388
  motions: DEFAULT_MOTIONS
4898
5389
  };
4899
- const valid = (0, import_format6.parseIkiModel)(model);
5390
+ const valid = (0, import_format8.parseIkiModel)(model);
4900
5391
  if (hasNose && options.onTurnSolved) {
4901
5392
  options.onTurnSolved(
4902
5393
  turnReport(
@@ -4911,14 +5402,14 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4911
5402
  function turnWarps(mesh, b, fieldOf) {
4912
5403
  return [
4913
5404
  localWarp(
4914
- import_format6.StandardParameter.AngleX,
5405
+ import_format8.StandardParameter.AngleX,
4915
5406
  mesh,
4916
5407
  b,
4917
5408
  X_STOPS,
4918
5409
  ([x, y], v, i) => v === 0 ? [0, 0] : [fieldOf(i)(x, y, v, 0)[0], 0]
4919
5410
  ),
4920
5411
  localWarp(
4921
- import_format6.StandardParameter.AngleY,
5412
+ import_format8.StandardParameter.AngleY,
4922
5413
  mesh,
4923
5414
  b,
4924
5415
  Y_STOPS,
@@ -4966,16 +5457,22 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
4966
5457
  }
4967
5458
  if (mesh !== void 0) part.mesh = mesh;
4968
5459
  const side = spec.role.endsWith("_L") ? "L" : "R";
4969
- const eyeOpen = side === "L" ? import_format6.StandardParameter.EyeOpenLeft : import_format6.StandardParameter.EyeOpenRight;
5460
+ const eyeOpen = side === "L" ? import_format8.StandardParameter.EyeOpenLeft : import_format8.StandardParameter.EyeOpenRight;
4970
5461
  const hh = frame.hh;
4971
5462
  switch (spec.role) {
4972
5463
  case "face":
4973
5464
  if (region?.some((r) => r === "neck")) {
4974
5465
  const isNeck = region.map((r) => r === "neck");
4975
5466
  warps.push(
4976
- tiltHang(mesh, b, pivot, (_y, i) => isNeck[i] ? 1 : 0),
5467
+ tiltHang(
5468
+ mesh,
5469
+ b,
5470
+ pivot,
5471
+ (_y, i) => isNeck[i] ? 1 : 0,
5472
+ headOwnRoll(byRole.has("body"))
5473
+ ),
4977
5474
  localWarp(
4978
- import_format6.StandardParameter.Breath,
5475
+ import_format8.StandardParameter.Breath,
4979
5476
  mesh,
4980
5477
  b,
4981
5478
  [0, 1],
@@ -5022,8 +5519,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5022
5519
  if (byRole.has("mouth_open")) {
5023
5520
  warps.push(mouthWiden(mesh, b));
5024
5521
  bindings.push(
5025
- { parameter: import_format6.StandardParameter.MouthOpen, channel: "opacity", from: 1, to: 0 },
5026
- { parameter: import_format6.StandardParameter.MouthOpen, channel: "opacity", from: 1, to: 0 }
5522
+ { parameter: import_format8.StandardParameter.MouthOpen, channel: "opacity", from: 1, to: 0 },
5523
+ { parameter: import_format8.StandardParameter.MouthOpen, channel: "opacity", from: 1, to: 0 }
5027
5524
  );
5028
5525
  extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
5029
5526
  } else {
@@ -5033,19 +5530,19 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5033
5530
  );
5034
5531
  bindings.push(
5035
5532
  {
5036
- parameter: import_format6.StandardParameter.MouthOpen,
5533
+ parameter: import_format8.StandardParameter.MouthOpen,
5037
5534
  channel: "scaleY",
5038
5535
  from: 0,
5039
5536
  to: roundTo(grow, 0.01)
5040
5537
  },
5041
5538
  {
5042
- parameter: import_format6.StandardParameter.MouthOpen,
5539
+ parameter: import_format8.StandardParameter.MouthOpen,
5043
5540
  channel: "scaleX",
5044
5541
  from: 0,
5045
5542
  to: roundTo(AMPLITUDE.mouthOpenWidth - 1, 0.01)
5046
5543
  },
5047
5544
  {
5048
- parameter: import_format6.StandardParameter.MouthOpen,
5545
+ parameter: import_format8.StandardParameter.MouthOpen,
5049
5546
  channel: "translateY",
5050
5547
  from: 0,
5051
5548
  to: -roundTo(grow * bh(b) / 2, 0.01)
@@ -5057,7 +5554,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5057
5554
  case "mouth_open":
5058
5555
  warps.push(mouthForm(mesh, b), mouthOpenGrow(mesh, b));
5059
5556
  bindings.push({
5060
- parameter: import_format6.StandardParameter.MouthOpen,
5557
+ parameter: import_format8.StandardParameter.MouthOpen,
5061
5558
  channel: "opacity",
5062
5559
  from: 0,
5063
5560
  to: 1
@@ -5067,7 +5564,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5067
5564
  case "blush_L":
5068
5565
  case "blush_R":
5069
5566
  bindings.push({
5070
- parameter: import_format6.StandardParameter.Cheek,
5567
+ parameter: import_format8.StandardParameter.Cheek,
5071
5568
  channel: "opacity",
5072
5569
  from: AMPLITUDE.blushRest,
5073
5570
  to: 1
@@ -5078,8 +5575,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5078
5575
  if (byRole.has("hair_front")) {
5079
5576
  const amp = style.sway * AMPLITUDE.sway * hh;
5080
5577
  warps.push(
5081
- hairSway(import_format6.StandardParameter.HairSwayX, mesh, b, amp),
5082
- hairSway(import_format6.StandardParameter.HairSwayZ, mesh, b, amp)
5578
+ hairSway(import_format8.StandardParameter.HairSwayX, mesh, b, amp),
5579
+ hairSway(import_format8.StandardParameter.HairSwayZ, mesh, b, amp)
5083
5580
  );
5084
5581
  }
5085
5582
  warps.push(tiltHang(mesh, b, pivot, hairHangWeight(frame.chinY, b.y0)));
@@ -5152,33 +5649,24 @@ function meshScale(role, frame) {
5152
5649
  return Infinity;
5153
5650
  }
5154
5651
  }
5155
- function deformers(frame, grids, body) {
5652
+ function deformers(frame, grids, bodyWarp, arms) {
5156
5653
  const out = [];
5157
- if (body !== void 0) {
5158
- out.push({
5159
- id: "bodyDeformer",
5160
- pivot: { x: roundTo(cx(body), 0.01), y: roundTo(body.y1, 0.01) },
5161
- bindings: [
5162
- {
5163
- parameter: import_format6.StandardParameter.Breath,
5164
- channel: "translateY",
5165
- from: 0,
5166
- to: roundTo(AMPLITUDE.breathBody * frame.hh, 0.01)
5167
- }
5168
- ]
5169
- });
5170
- }
5654
+ if (bodyWarp !== void 0) out.push(bodyWarp);
5655
+ for (const [role, g] of arms) out.push(...armDeformers(role, g));
5656
+ const hasBody = bodyWarp !== void 0;
5657
+ const roll = headOwnRoll(hasBody);
5171
5658
  out.push({
5172
5659
  id: "headDeformer",
5660
+ ...hasBody ? { parent: BODY_WARP_ID } : {},
5173
5661
  pivot: { x: roundTo(frame.axisX, 0.01), y: roundTo(frame.chinY, 0.01) },
5174
5662
  bindings: [
5175
5663
  // AngleZ is clockwise-positive (Live2D's); a rotation is CCW-positive.
5176
- { parameter: import_format6.StandardParameter.AngleZ, channel: "rotate", from: ROLL_DEG, to: -ROLL_DEG },
5664
+ { parameter: import_format8.StandardParameter.AngleZ, channel: "rotate", from: roll, to: -roll },
5177
5665
  {
5178
- parameter: import_format6.StandardParameter.Breath,
5666
+ parameter: import_format8.StandardParameter.Breath,
5179
5667
  channel: "translateY",
5180
5668
  from: 0,
5181
- to: roundTo(AMPLITUDE.breathHead * frame.hh, 0.01)
5669
+ to: roundTo(headOwnBreath(hasBody) * frame.hh, 0.01)
5182
5670
  }
5183
5671
  ]
5184
5672
  });
@@ -5193,8 +5681,8 @@ function deformers(frame, grids, body) {
5193
5681
  points: g.lattice.points
5194
5682
  },
5195
5683
  warp2d: {
5196
- parameter: import_format6.StandardParameter.AngleX,
5197
- parameterY: import_format6.StandardParameter.AngleY,
5684
+ parameter: import_format8.StandardParameter.AngleX,
5685
+ parameterY: import_format8.StandardParameter.AngleY,
5198
5686
  valuesX: X_STOPS,
5199
5687
  valuesY: Y_STOPS,
5200
5688
  keyforms2d: g.keyforms.map((offsets) => ({ offsets }))
@@ -5207,16 +5695,16 @@ function hairPhysics() {
5207
5695
  return [
5208
5696
  {
5209
5697
  id: "hairSway",
5210
- input: { parameter: import_format6.StandardParameter.AngleX, weight: 1 },
5211
- output: { parameter: import_format6.StandardParameter.HairSwayX, scale: 5 },
5698
+ input: { parameter: import_format8.StandardParameter.AngleX, weight: 1 },
5699
+ output: { parameter: import_format8.StandardParameter.HairSwayX, scale: 5 },
5212
5700
  mass: 1,
5213
5701
  stiffness: 30,
5214
5702
  damping: 3
5215
5703
  },
5216
5704
  {
5217
5705
  id: "hairTilt",
5218
- input: { parameter: import_format6.StandardParameter.AngleZ, weight: 1 },
5219
- output: { parameter: import_format6.StandardParameter.HairSwayZ, scale: 8 },
5706
+ input: { parameter: import_format8.StandardParameter.AngleZ, weight: 1 },
5707
+ output: { parameter: import_format8.StandardParameter.HairSwayZ, scale: 8 },
5220
5708
  mass: 1,
5221
5709
  stiffness: 25,
5222
5710
  damping: 2.5
@@ -5226,32 +5714,46 @@ function hairPhysics() {
5226
5714
  function declareParameters(roles) {
5227
5715
  const out = [];
5228
5716
  const add = (id, name, min, max, def) => out.push({ id, name, min, max, default: def });
5229
- add(import_format6.StandardParameter.AngleX, "Head Angle", -30, 30, 0);
5230
- add(import_format6.StandardParameter.AngleY, "Head Angle Y", -30, 30, 0);
5231
- add(import_format6.StandardParameter.AngleZ, "Head Angle Z", -30, 30, 0);
5232
- add(import_format6.StandardParameter.EyeOpenLeft, "Eye L", 0, 1, 1);
5233
- add(import_format6.StandardParameter.EyeOpenRight, "Eye R", 0, 1, 1);
5717
+ add(import_format8.StandardParameter.AngleX, "Head Angle", -30, 30, 0);
5718
+ add(import_format8.StandardParameter.AngleY, "Head Angle Y", -30, 30, 0);
5719
+ add(import_format8.StandardParameter.AngleZ, "Head Angle Z", -30, 30, 0);
5720
+ if (roles.has("body")) {
5721
+ add(import_format8.StandardParameter.BodyAngleX, "Body Angle X", -10, 10, 0);
5722
+ add(import_format8.StandardParameter.BodyAngleY, "Body Angle Y", -10, 10, 0);
5723
+ add(import_format8.StandardParameter.BodyAngleZ, "Body Angle Z", -10, 10, 0);
5724
+ }
5725
+ const [armMin, armMax] = ARM_RANGE;
5726
+ if (roles.has("arm_L")) {
5727
+ add(import_format8.StandardParameter.ArmLeft, "Arm L", armMin, armMax, 0);
5728
+ add(import_format8.StandardParameter.ElbowLeft, "Elbow L", armMin, armMax, 0);
5729
+ }
5730
+ if (roles.has("arm_R")) {
5731
+ add(import_format8.StandardParameter.ArmRight, "Arm R", armMin, armMax, 0);
5732
+ add(import_format8.StandardParameter.ElbowRight, "Elbow R", armMin, armMax, 0);
5733
+ }
5734
+ add(import_format8.StandardParameter.EyeOpenLeft, "Eye L", 0, 1, 1);
5735
+ add(import_format8.StandardParameter.EyeOpenRight, "Eye R", 0, 1, 1);
5234
5736
  if ([...roles].some((r) => /^(iris|pupil|highlight)_/.test(r))) {
5235
- add(import_format6.StandardParameter.EyeballX, "Gaze X", -1, 1, 0);
5236
- add(import_format6.StandardParameter.EyeballY, "Gaze Y", -1, 1, 0);
5737
+ add(import_format8.StandardParameter.EyeballX, "Gaze X", -1, 1, 0);
5738
+ add(import_format8.StandardParameter.EyeballY, "Gaze Y", -1, 1, 0);
5237
5739
  }
5238
- add(import_format6.StandardParameter.MouthOpen, "Mouth Open", 0, 1, 0);
5239
- add(import_format6.StandardParameter.MouthForm, "Mouth Form", -1, 1, 0);
5740
+ add(import_format8.StandardParameter.MouthOpen, "Mouth Open", 0, 1, 0);
5741
+ add(import_format8.StandardParameter.MouthForm, "Mouth Form", -1, 1, 0);
5240
5742
  if (roles.has("brow_L")) {
5241
- add(import_format6.StandardParameter.BrowLeftY, "Brow L Y", -1, 1, 0);
5242
- add(import_format6.StandardParameter.BrowLeftAngle, "Brow L Angle", -1, 1, 0);
5743
+ add(import_format8.StandardParameter.BrowLeftY, "Brow L Y", -1, 1, 0);
5744
+ add(import_format8.StandardParameter.BrowLeftAngle, "Brow L Angle", -1, 1, 0);
5243
5745
  }
5244
5746
  if (roles.has("brow_R")) {
5245
- add(import_format6.StandardParameter.BrowRightY, "Brow R Y", -1, 1, 0);
5246
- add(import_format6.StandardParameter.BrowRightAngle, "Brow R Angle", -1, 1, 0);
5747
+ add(import_format8.StandardParameter.BrowRightY, "Brow R Y", -1, 1, 0);
5748
+ add(import_format8.StandardParameter.BrowRightAngle, "Brow R Angle", -1, 1, 0);
5247
5749
  }
5248
5750
  if (roles.has("blush_L") || roles.has("blush_R")) {
5249
- add(import_format6.StandardParameter.Cheek, "Cheek", 0, 1, 0);
5751
+ add(import_format8.StandardParameter.Cheek, "Cheek", 0, 1, 0);
5250
5752
  }
5251
- add(import_format6.StandardParameter.Breath, "Breath", 0, 1, 0);
5753
+ add(import_format8.StandardParameter.Breath, "Breath", 0, 1, 0);
5252
5754
  if (roles.has("hair_front")) {
5253
- add(import_format6.StandardParameter.HairSwayX, "Hair Sway X", -SWAY_RANGE, SWAY_RANGE, 0);
5254
- add(import_format6.StandardParameter.HairSwayZ, "Hair Sway Z", -SWAY_RANGE, SWAY_RANGE, 0);
5755
+ add(import_format8.StandardParameter.HairSwayX, "Hair Sway X", -SWAY_RANGE, SWAY_RANGE, 0);
5756
+ add(import_format8.StandardParameter.HairSwayZ, "Hair Sway Z", -SWAY_RANGE, SWAY_RANGE, 0);
5255
5757
  }
5256
5758
  return out;
5257
5759
  }
@@ -5270,6 +5772,7 @@ function declareParameters(roles) {
5270
5772
  DeletePhysicsRig,
5271
5773
  EditorDocument,
5272
5774
  HEAD_BAND,
5775
+ LayerGeometryError,
5273
5776
  SPECK_CORE_FRACTION,
5274
5777
  SetDeformerBindings,
5275
5778
  SetDeformerParent,
@@ -5288,6 +5791,7 @@ function declareParameters(roles) {
5288
5791
  SetPhysicsRig,
5289
5792
  TurnTargetError,
5290
5793
  UV_INSET_PX,
5794
+ armGeometry,
5291
5795
  captureBindingEndpoint,
5292
5796
  computeGridOffsets,
5293
5797
  createDefaultMatrixDeformer,
@@ -5304,6 +5808,7 @@ function declareParameters(roles) {
5304
5808
  isSpeckCore,
5305
5809
  packAtlas,
5306
5810
  parseLayerRoles,
5811
+ partIdsOfRole,
5307
5812
  upsertGridKeyform,
5308
5813
  uvRectFor,
5309
5814
  validateDeformerDelete,