@ikijs/editor 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1910,7 +1910,8 @@ var PROFILE_VALUES = {
1910
1910
  mouthWiden: 1.2,
1911
1911
  brow: 0.15,
1912
1912
  breath: 0.03,
1913
- sway: 0.08
1913
+ sway: 0.08,
1914
+ blushRest: 0.4
1914
1915
  };
1915
1916
  function deriveProfile(v) {
1916
1917
  const farLead = v.lead * (1 - v.fore / 2);
@@ -1997,6 +1998,8 @@ function deriveProfile(v) {
1997
1998
  breathHead: v.breath * 2 / 3,
1998
1999
  /** Hair tip travel at full sway (±20), hh. */
1999
2000
  sway: v.sway,
2001
+ /** The blush's opacity at Cheek 0; at Cheek 1 it is as drawn. */
2002
+ blushRest: v.blushRest,
2000
2003
  /** The neck under the face reaches this far above the jaw, hh — what the
2001
2004
  * chin uncovers as it slides off it: half again the chin's full-turn
2002
2005
  * slide, to the next 0.05. */
@@ -2058,6 +2061,8 @@ var ROLE_TABLE = [
2058
2061
  { role: "pupil_R", family: "eye_R" },
2059
2062
  { role: "highlight_L", family: "eye_L" },
2060
2063
  { role: "highlight_R", family: "eye_R" },
2064
+ { role: "lash_lower_L", family: "eye_L" },
2065
+ { role: "lash_lower_R", family: "eye_R" },
2061
2066
  { role: "lash_L", family: "eye_L" },
2062
2067
  { role: "lash_R", family: "eye_R" },
2063
2068
  { role: "brow_L", family: "brow_L" },
@@ -2114,10 +2119,161 @@ function parseLayerRoles(fileNames) {
2114
2119
  // src/auto-rig/generate.ts
2115
2120
  import {
2116
2121
  IKI_FORMAT_VERSION as IKI_FORMAT_VERSION2,
2117
- StandardParameter as P3,
2122
+ StandardParameter as P4,
2118
2123
  parseIkiModel as parseIkiModel3
2119
2124
  } from "@ikijs/format";
2120
2125
 
2126
+ // src/auto-rig/animations.ts
2127
+ import {
2128
+ StandardParameter as P
2129
+ } from "@ikijs/format";
2130
+ function browsY(y) {
2131
+ return [
2132
+ { parameter: P.BrowLeftY, value: y, blend: "add" },
2133
+ { parameter: P.BrowRightY, value: y, blend: "add" }
2134
+ ];
2135
+ }
2136
+ function browsTilt(innerDown) {
2137
+ return [
2138
+ { parameter: P.BrowLeftAngle, value: innerDown, blend: "add" },
2139
+ { parameter: P.BrowRightAngle, value: -innerDown, blend: "add" }
2140
+ ];
2141
+ }
2142
+ function cheek(value) {
2143
+ return { parameter: P.Cheek, value, blend: "add" };
2144
+ }
2145
+ var BROWS = [P.BrowLeftY, P.BrowRightY, P.BrowLeftAngle, P.BrowRightAngle];
2146
+ var EXPRESSIONS = [
2147
+ {
2148
+ id: "smile",
2149
+ description: "Smiling and pleased: warm, friendly, content. For greetings, thanks, agreement and gentle happiness.",
2150
+ parameters: [
2151
+ { parameter: P.MouthForm, value: 1, blend: "add" },
2152
+ ...browsY(0.25),
2153
+ cheek(0.4)
2154
+ ]
2155
+ },
2156
+ {
2157
+ id: "laugh",
2158
+ description: "Laughing, eyes shut and mouth open: delighted, amused. For jokes, playfulness and big joy.",
2159
+ parameters: [
2160
+ { parameter: P.EyeOpenLeft, value: 0, blend: "multiply" },
2161
+ { parameter: P.EyeOpenRight, value: 0, blend: "multiply" },
2162
+ { parameter: P.MouthForm, value: 1, blend: "add" },
2163
+ { parameter: P.MouthOpen, value: 0.8, blend: "overwrite" },
2164
+ ...browsY(0.4),
2165
+ cheek(0.65)
2166
+ ]
2167
+ },
2168
+ {
2169
+ id: "angry",
2170
+ description: "Angry or annoyed, frowning. For irritation, frustration, indignation or scolding.",
2171
+ needs: BROWS,
2172
+ parameters: [
2173
+ ...browsY(-0.5),
2174
+ ...browsTilt(0.8),
2175
+ { parameter: P.MouthForm, value: -0.9, blend: "add" }
2176
+ ]
2177
+ },
2178
+ {
2179
+ id: "sad",
2180
+ description: "Sad or disappointed, downcast. For sorrow, regret, apology or sympathy.",
2181
+ needs: BROWS,
2182
+ parameters: [
2183
+ ...browsY(0.35),
2184
+ ...browsTilt(-0.8),
2185
+ { parameter: P.MouthForm, value: -0.8, blend: "add" }
2186
+ ]
2187
+ },
2188
+ {
2189
+ id: "surprised",
2190
+ description: "Surprised, mouth dropped open: startled, amazed. For shock, sudden news or disbelief.",
2191
+ parameters: [
2192
+ ...browsY(0.8),
2193
+ { parameter: P.MouthOpen, value: 0.9, blend: "overwrite" }
2194
+ ]
2195
+ },
2196
+ {
2197
+ id: "shy",
2198
+ description: "Shy or embarrassed: bashful, flustered. For being praised, teased or caught off guard.",
2199
+ needs: [P.Cheek],
2200
+ parameters: [
2201
+ cheek(1),
2202
+ // Gaze down.
2203
+ { parameter: P.EyeballY, value: -0.65, blend: "add" },
2204
+ { parameter: P.MouthForm, value: 0.4, blend: "add" },
2205
+ ...browsTilt(-0.25)
2206
+ ]
2207
+ }
2208
+ ];
2209
+ function defaultExpressions(declared) {
2210
+ return EXPRESSIONS.filter(
2211
+ (e) => e.needs === void 0 || e.needs.some((id) => declared.has(id))
2212
+ ).map(({ needs: _needs, ...e }) => ({
2213
+ ...e,
2214
+ parameters: e.parameters.filter((t) => declared.has(t.parameter))
2215
+ }));
2216
+ }
2217
+ var DEFAULT_MOTIONS = {
2218
+ Nod: [
2219
+ {
2220
+ description: "Nods yes: agreement, acknowledgement, understanding.",
2221
+ duration: 1,
2222
+ curves: [
2223
+ {
2224
+ parameter: P.AngleY,
2225
+ keys: [
2226
+ [0, 0],
2227
+ [0.3, -18],
2228
+ [0.6, 4],
2229
+ [0.8, -1],
2230
+ [1, 0]
2231
+ ]
2232
+ }
2233
+ ]
2234
+ }
2235
+ ],
2236
+ Shake: [
2237
+ {
2238
+ description: "Shakes the head no: disagreement, refusal, disbelief.",
2239
+ duration: 1.2,
2240
+ // The first swing peaks at 0.2 s, which the default fade would still
2241
+ // be damping.
2242
+ fadeIn: 0.15,
2243
+ curves: [
2244
+ {
2245
+ parameter: P.AngleX,
2246
+ keys: [
2247
+ [0, 0],
2248
+ [0.2, -18],
2249
+ [0.45, 18],
2250
+ [0.7, -16],
2251
+ [0.95, 10],
2252
+ [1.2, 0]
2253
+ ]
2254
+ }
2255
+ ]
2256
+ }
2257
+ ],
2258
+ Tilt: [
2259
+ {
2260
+ description: "Tilts the head to one side and back: curiosity, puzzlement, a question.",
2261
+ duration: 1.4,
2262
+ curves: [
2263
+ {
2264
+ parameter: P.AngleZ,
2265
+ keys: [
2266
+ [0, 0],
2267
+ [0.35, 20],
2268
+ [1, 20],
2269
+ [1.4, 0]
2270
+ ]
2271
+ }
2272
+ ]
2273
+ }
2274
+ ]
2275
+ };
2276
+
2121
2277
  // src/auto-rig/layout.ts
2122
2278
  function boxOfLayer(layer) {
2123
2279
  return boxOfPixels(layer.bbox, layer.canvasW, layer.canvasH);
@@ -2411,7 +2567,7 @@ function checkStrandEdges(strand, irises, hair) {
2411
2567
  }
2412
2568
 
2413
2569
  // src/auto-rig/context.ts
2414
- import { StandardParameter as P } from "@ikijs/format";
2570
+ import { StandardParameter as P2 } from "@ikijs/format";
2415
2571
 
2416
2572
  // src/auto-rig/grid.ts
2417
2573
  var X_STOPS = [-30, 0, 30];
@@ -3627,7 +3783,7 @@ function renderedLander(turn, parts, grids, from = /* @__PURE__ */ new Map()) {
3627
3783
  const family = roleSpec(role).family;
3628
3784
  const part = parts.find((p) => p.id === role);
3629
3785
  const g = grids.get(family);
3630
- const warp = part?.warps?.find((w) => w.parameter === P.AngleX);
3786
+ const warp = part?.warps?.find((w) => w.parameter === P2.AngleX);
3631
3787
  if (part === void 0 || part.mesh === void 0 || !g && !warp) {
3632
3788
  return x + field(role)(x, y, ax, 0)[0];
3633
3789
  }
@@ -4338,7 +4494,7 @@ function buildTurn(frame, landmarks, q) {
4338
4494
 
4339
4495
  // src/auto-rig/drivers.ts
4340
4496
  import {
4341
- StandardParameter as P2
4497
+ StandardParameter as P3
4342
4498
  } from "@ikijs/format";
4343
4499
  var SWAY_RANGE = 20;
4344
4500
  var SWAY_CURVE = 1.4;
@@ -4391,12 +4547,12 @@ function motionReach(warps, mesh, box, extra = [0, 0]) {
4391
4547
  }
4392
4548
  return out;
4393
4549
  }
4394
- function blinkFold(param, mesh, eye, travel) {
4550
+ function blinkFold(param, mesh, box, travel, eye = box) {
4395
4551
  const yc = eye.y1 - travel * bh(eye);
4396
4552
  return localWarp(
4397
4553
  param,
4398
4554
  mesh,
4399
- eye,
4555
+ box,
4400
4556
  [0, 1],
4401
4557
  ([, y], v) => v === 1 ? [0, 0] : [0, yc - y]
4402
4558
  );
@@ -4417,13 +4573,13 @@ function gazeBindings(irisW, share = 1) {
4417
4573
  const gy = AMPLITUDE.gazeY * irisW * share;
4418
4574
  return [
4419
4575
  {
4420
- parameter: P2.EyeballX,
4576
+ parameter: P3.EyeballX,
4421
4577
  channel: "translateX",
4422
4578
  from: -round1(gx),
4423
4579
  to: round1(gx)
4424
4580
  },
4425
4581
  {
4426
- parameter: P2.EyeballY,
4582
+ parameter: P3.EyeballY,
4427
4583
  channel: "translateY",
4428
4584
  from: -round1(gy),
4429
4585
  to: round1(gy)
@@ -4437,8 +4593,8 @@ function browReach(hh) {
4437
4593
  return AMPLITUDE.brow * hh;
4438
4594
  }
4439
4595
  function browBindings(side, reach) {
4440
- const y = side === "L" ? P2.BrowLeftY : P2.BrowRightY;
4441
- const a = side === "L" ? P2.BrowLeftAngle : P2.BrowRightAngle;
4596
+ const y = side === "L" ? P3.BrowLeftY : P3.BrowRightY;
4597
+ const a = side === "L" ? P3.BrowLeftAngle : P3.BrowRightAngle;
4442
4598
  return [
4443
4599
  {
4444
4600
  parameter: y,
@@ -4454,7 +4610,7 @@ function mouthForm(mesh, box) {
4454
4610
  const a = 0.3 * bh(box);
4455
4611
  const c = cx(box);
4456
4612
  const half = bw(box) / 2;
4457
- return localWarp(P2.MouthForm, mesh, box, [-1, 0, 1], ([x], v) => {
4613
+ return localWarp(P3.MouthForm, mesh, box, [-1, 0, 1], ([x], v) => {
4458
4614
  if (v === 0) return [0, 0];
4459
4615
  const u = (x - c) / half;
4460
4616
  const widen = v > 0 ? 0.05 : 0.03;
@@ -4464,7 +4620,7 @@ function mouthForm(mesh, box) {
4464
4620
  function mouthOpenGrow(mesh, box) {
4465
4621
  const c = cx(box);
4466
4622
  return localWarp(
4467
- P2.MouthOpen,
4623
+ P3.MouthOpen,
4468
4624
  mesh,
4469
4625
  box,
4470
4626
  [0, 1],
@@ -4473,7 +4629,7 @@ function mouthOpenGrow(mesh, box) {
4473
4629
  }
4474
4630
  function mouthWiden(mesh, box) {
4475
4631
  const c = cx(box);
4476
- return localWarp(P2.MouthOpen, mesh, box, [0, 1], ([x], v) => [
4632
+ return localWarp(P3.MouthOpen, mesh, box, [0, 1], ([x], v) => [
4477
4633
  v * (AMPLITUDE.mouthOpenWidth - 1) * (x - c),
4478
4634
  0
4479
4635
  ]);
@@ -4491,7 +4647,7 @@ function hairSway(param, mesh, box, amp) {
4491
4647
  );
4492
4648
  }
4493
4649
  function tiltHang(mesh, box, pivot, weight) {
4494
- return localWarp(P2.AngleZ, mesh, box, Z_STOPS, ([x, y], v, i) => {
4650
+ return localWarp(P3.AngleZ, mesh, box, Z_STOPS, ([x, y], v, i) => {
4495
4651
  const g = weight(y, i);
4496
4652
  if (g === 0 || v === 0) return [0, 0];
4497
4653
  const a = -g * rollOf(v);
@@ -4644,14 +4800,17 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4644
4800
  ...part.warps ?? []
4645
4801
  ];
4646
4802
  }
4803
+ const parameters = declareParameters(new Set(byRole.keys()));
4647
4804
  const model = {
4648
4805
  version: IKI_FORMAT_VERSION2,
4649
4806
  name: "auto-rigged",
4650
4807
  canvas: { width: canvas.width, height: canvas.height },
4651
- parameters: declareParameters(new Set(byRole.keys())),
4808
+ parameters,
4652
4809
  parts,
4653
4810
  deformers: deformers(frame, grids, has("body") ? box("body") : void 0),
4654
- ...has("hair_front") ? { physics: hairPhysics() } : {}
4811
+ ...has("hair_front") ? { physics: hairPhysics() } : {},
4812
+ expressions: defaultExpressions(new Set(parameters.map((p) => p.id))),
4813
+ motions: DEFAULT_MOTIONS
4655
4814
  };
4656
4815
  const valid = parseIkiModel3(model);
4657
4816
  if (hasNose && options.onTurnSolved) {
@@ -4668,14 +4827,14 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4668
4827
  function turnWarps(mesh, b, fieldOf) {
4669
4828
  return [
4670
4829
  localWarp(
4671
- P3.AngleX,
4830
+ P4.AngleX,
4672
4831
  mesh,
4673
4832
  b,
4674
4833
  X_STOPS,
4675
4834
  ([x, y], v, i) => v === 0 ? [0, 0] : [fieldOf(i)(x, y, v, 0)[0], 0]
4676
4835
  ),
4677
4836
  localWarp(
4678
- P3.AngleY,
4837
+ P4.AngleY,
4679
4838
  mesh,
4680
4839
  b,
4681
4840
  Y_STOPS,
@@ -4723,7 +4882,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
4723
4882
  }
4724
4883
  if (mesh !== void 0) part.mesh = mesh;
4725
4884
  const side = spec.role.endsWith("_L") ? "L" : "R";
4726
- const eyeOpen = side === "L" ? P3.EyeOpenLeft : P3.EyeOpenRight;
4885
+ const eyeOpen = side === "L" ? P4.EyeOpenLeft : P4.EyeOpenRight;
4727
4886
  const hh = frame.hh;
4728
4887
  switch (spec.role) {
4729
4888
  case "face":
@@ -4732,7 +4891,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
4732
4891
  warps.push(
4733
4892
  tiltHang(mesh, b, pivot, (_y, i) => isNeck[i] ? 1 : 0),
4734
4893
  localWarp(
4735
- P3.Breath,
4894
+ P4.Breath,
4736
4895
  mesh,
4737
4896
  b,
4738
4897
  [0, 1],
@@ -4745,6 +4904,10 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
4745
4904
  case "eye_R":
4746
4905
  warps.push(blinkFold(eyeOpen, mesh, b, style.blink));
4747
4906
  break;
4907
+ case "lash_lower_L":
4908
+ case "lash_lower_R":
4909
+ warps.push(blinkFold(eyeOpen, mesh, b, style.blink, box(`eye_${side}`)));
4910
+ break;
4748
4911
  case "lash_L":
4749
4912
  case "lash_R":
4750
4913
  warps.push(lashFold(eyeOpen, mesh, b, box(`eye_${side}`), style.blink));
@@ -4775,8 +4938,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
4775
4938
  if (byRole.has("mouth_open")) {
4776
4939
  warps.push(mouthWiden(mesh, b));
4777
4940
  bindings.push(
4778
- { parameter: P3.MouthOpen, channel: "opacity", from: 1, to: 0 },
4779
- { parameter: P3.MouthOpen, channel: "opacity", from: 1, to: 0 }
4941
+ { parameter: P4.MouthOpen, channel: "opacity", from: 1, to: 0 },
4942
+ { parameter: P4.MouthOpen, channel: "opacity", from: 1, to: 0 }
4780
4943
  );
4781
4944
  extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
4782
4945
  } else {
@@ -4786,19 +4949,19 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
4786
4949
  );
4787
4950
  bindings.push(
4788
4951
  {
4789
- parameter: P3.MouthOpen,
4952
+ parameter: P4.MouthOpen,
4790
4953
  channel: "scaleY",
4791
4954
  from: 0,
4792
4955
  to: roundTo(grow, 0.01)
4793
4956
  },
4794
4957
  {
4795
- parameter: P3.MouthOpen,
4958
+ parameter: P4.MouthOpen,
4796
4959
  channel: "scaleX",
4797
4960
  from: 0,
4798
4961
  to: roundTo(AMPLITUDE.mouthOpenWidth - 1, 0.01)
4799
4962
  },
4800
4963
  {
4801
- parameter: P3.MouthOpen,
4964
+ parameter: P4.MouthOpen,
4802
4965
  channel: "translateY",
4803
4966
  from: 0,
4804
4967
  to: -roundTo(grow * bh(b) / 2, 0.01)
@@ -4810,20 +4973,29 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
4810
4973
  case "mouth_open":
4811
4974
  warps.push(mouthForm(mesh, b), mouthOpenGrow(mesh, b));
4812
4975
  bindings.push({
4813
- parameter: P3.MouthOpen,
4976
+ parameter: P4.MouthOpen,
4814
4977
  channel: "opacity",
4815
4978
  from: 0,
4816
4979
  to: 1
4817
4980
  });
4818
4981
  extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
4819
4982
  break;
4983
+ case "blush_L":
4984
+ case "blush_R":
4985
+ bindings.push({
4986
+ parameter: P4.Cheek,
4987
+ channel: "opacity",
4988
+ from: AMPLITUDE.blushRest,
4989
+ to: 1
4990
+ });
4991
+ break;
4820
4992
  case "hair_front":
4821
4993
  case "hair_back":
4822
4994
  if (byRole.has("hair_front")) {
4823
4995
  const amp = style.sway * AMPLITUDE.sway * hh;
4824
4996
  warps.push(
4825
- hairSway(P3.HairSwayX, mesh, b, amp),
4826
- hairSway(P3.HairSwayZ, mesh, b, amp)
4997
+ hairSway(P4.HairSwayX, mesh, b, amp),
4998
+ hairSway(P4.HairSwayZ, mesh, b, amp)
4827
4999
  );
4828
5000
  }
4829
5001
  warps.push(tiltHang(mesh, b, pivot, hairHangWeight(frame.chinY, b.y0)));
@@ -4904,7 +5076,7 @@ function deformers(frame, grids, body) {
4904
5076
  pivot: { x: roundTo(cx(body), 0.01), y: roundTo(body.y1, 0.01) },
4905
5077
  bindings: [
4906
5078
  {
4907
- parameter: P3.Breath,
5079
+ parameter: P4.Breath,
4908
5080
  channel: "translateY",
4909
5081
  from: 0,
4910
5082
  to: roundTo(AMPLITUDE.breathBody * frame.hh, 0.01)
@@ -4917,9 +5089,9 @@ function deformers(frame, grids, body) {
4917
5089
  pivot: { x: roundTo(frame.axisX, 0.01), y: roundTo(frame.chinY, 0.01) },
4918
5090
  bindings: [
4919
5091
  // AngleZ is clockwise-positive (Live2D's); a rotation is CCW-positive.
4920
- { parameter: P3.AngleZ, channel: "rotate", from: ROLL_DEG, to: -ROLL_DEG },
5092
+ { parameter: P4.AngleZ, channel: "rotate", from: ROLL_DEG, to: -ROLL_DEG },
4921
5093
  {
4922
- parameter: P3.Breath,
5094
+ parameter: P4.Breath,
4923
5095
  channel: "translateY",
4924
5096
  from: 0,
4925
5097
  to: roundTo(AMPLITUDE.breathHead * frame.hh, 0.01)
@@ -4937,8 +5109,8 @@ function deformers(frame, grids, body) {
4937
5109
  points: g.lattice.points
4938
5110
  },
4939
5111
  warp2d: {
4940
- parameter: P3.AngleX,
4941
- parameterY: P3.AngleY,
5112
+ parameter: P4.AngleX,
5113
+ parameterY: P4.AngleY,
4942
5114
  valuesX: X_STOPS,
4943
5115
  valuesY: Y_STOPS,
4944
5116
  keyforms2d: g.keyforms.map((offsets) => ({ offsets }))
@@ -4951,16 +5123,16 @@ function hairPhysics() {
4951
5123
  return [
4952
5124
  {
4953
5125
  id: "hairSway",
4954
- input: { parameter: P3.AngleX, weight: 1 },
4955
- output: { parameter: P3.HairSwayX, scale: 5 },
5126
+ input: { parameter: P4.AngleX, weight: 1 },
5127
+ output: { parameter: P4.HairSwayX, scale: 5 },
4956
5128
  mass: 1,
4957
5129
  stiffness: 30,
4958
5130
  damping: 3
4959
5131
  },
4960
5132
  {
4961
5133
  id: "hairTilt",
4962
- input: { parameter: P3.AngleZ, weight: 1 },
4963
- output: { parameter: P3.HairSwayZ, scale: 8 },
5134
+ input: { parameter: P4.AngleZ, weight: 1 },
5135
+ output: { parameter: P4.HairSwayZ, scale: 8 },
4964
5136
  mass: 1,
4965
5137
  stiffness: 25,
4966
5138
  damping: 2.5
@@ -4970,29 +5142,32 @@ function hairPhysics() {
4970
5142
  function declareParameters(roles) {
4971
5143
  const out = [];
4972
5144
  const add = (id, name, min, max, def) => out.push({ id, name, min, max, default: def });
4973
- add(P3.AngleX, "Head Angle", -30, 30, 0);
4974
- add(P3.AngleY, "Head Angle Y", -30, 30, 0);
4975
- add(P3.AngleZ, "Head Angle Z", -30, 30, 0);
4976
- add(P3.EyeOpenLeft, "Eye L", 0, 1, 1);
4977
- add(P3.EyeOpenRight, "Eye R", 0, 1, 1);
5145
+ add(P4.AngleX, "Head Angle", -30, 30, 0);
5146
+ add(P4.AngleY, "Head Angle Y", -30, 30, 0);
5147
+ add(P4.AngleZ, "Head Angle Z", -30, 30, 0);
5148
+ add(P4.EyeOpenLeft, "Eye L", 0, 1, 1);
5149
+ add(P4.EyeOpenRight, "Eye R", 0, 1, 1);
4978
5150
  if ([...roles].some((r) => /^(iris|pupil|highlight)_/.test(r))) {
4979
- add(P3.EyeballX, "Gaze X", -1, 1, 0);
4980
- add(P3.EyeballY, "Gaze Y", -1, 1, 0);
5151
+ add(P4.EyeballX, "Gaze X", -1, 1, 0);
5152
+ add(P4.EyeballY, "Gaze Y", -1, 1, 0);
4981
5153
  }
4982
- add(P3.MouthOpen, "Mouth Open", 0, 1, 0);
4983
- add(P3.MouthForm, "Mouth Form", -1, 1, 0);
5154
+ add(P4.MouthOpen, "Mouth Open", 0, 1, 0);
5155
+ add(P4.MouthForm, "Mouth Form", -1, 1, 0);
4984
5156
  if (roles.has("brow_L")) {
4985
- add(P3.BrowLeftY, "Brow L Y", -1, 1, 0);
4986
- add(P3.BrowLeftAngle, "Brow L Angle", -1, 1, 0);
5157
+ add(P4.BrowLeftY, "Brow L Y", -1, 1, 0);
5158
+ add(P4.BrowLeftAngle, "Brow L Angle", -1, 1, 0);
4987
5159
  }
4988
5160
  if (roles.has("brow_R")) {
4989
- add(P3.BrowRightY, "Brow R Y", -1, 1, 0);
4990
- add(P3.BrowRightAngle, "Brow R Angle", -1, 1, 0);
5161
+ add(P4.BrowRightY, "Brow R Y", -1, 1, 0);
5162
+ add(P4.BrowRightAngle, "Brow R Angle", -1, 1, 0);
5163
+ }
5164
+ if (roles.has("blush_L") || roles.has("blush_R")) {
5165
+ add(P4.Cheek, "Cheek", 0, 1, 0);
4991
5166
  }
4992
- add(P3.Breath, "Breath", 0, 1, 0);
5167
+ add(P4.Breath, "Breath", 0, 1, 0);
4993
5168
  if (roles.has("hair_front")) {
4994
- add(P3.HairSwayX, "Hair Sway X", -SWAY_RANGE, SWAY_RANGE, 0);
4995
- add(P3.HairSwayZ, "Hair Sway Z", -SWAY_RANGE, SWAY_RANGE, 0);
5169
+ add(P4.HairSwayX, "Hair Sway X", -SWAY_RANGE, SWAY_RANGE, 0);
5170
+ add(P4.HairSwayZ, "Hair Sway Z", -SWAY_RANGE, SWAY_RANGE, 0);
4996
5171
  }
4997
5172
  return out;
4998
5173
  }