@ikijs/editor 0.14.0 → 0.16.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
@@ -447,11 +447,24 @@ function validateDeformerReparent(deformers2, deformerId, newParentId) {
447
447
  `deformers."${deformerId}".parent "${newParentId}" is not a declared deformer`
448
448
  );
449
449
  }
450
- if (kindOf(parent) === "warp") {
450
+ if (kindOf(parent) === "warp" && kindOf(target) === "warp") {
451
451
  throw new Error(
452
- `deformers."${deformerId}".parent "${newParentId}" must be a matrix deformer (warp deformers cannot be parents)`
452
+ `deformers."${deformerId}".parent "${newParentId}" must be a matrix deformer (warp deformers cannot be nested under a warp deformer)`
453
453
  );
454
454
  }
455
+ if (parent.kind === "warp" && target.kind !== "warp") {
456
+ const { pivot } = target;
457
+ const { cols, rows, points } = parent.grid;
458
+ const xMin = points[0];
459
+ const xMax = points[2 * cols];
460
+ const yMax = points[1];
461
+ const yMin = points[rows * (cols + 1) * 2 + 1];
462
+ if (pivot.x < xMin || pivot.x > xMax || pivot.y < yMin || pivot.y > yMax) {
463
+ throw new Error(
464
+ `deformers."${deformerId}".pivot (${pivot.x}, ${pivot.y}) lies outside its warp parent "${newParentId}" rest grid x ${xMin}..${xMax}, y ${yMin}..${yMax}`
465
+ );
466
+ }
467
+ }
455
468
  const parentOf = /* @__PURE__ */ new Map();
456
469
  for (const d of deformers2) {
457
470
  if (d.parent !== void 0) parentOf.set(d.id, d.parent);
@@ -1910,7 +1923,8 @@ var PROFILE_VALUES = {
1910
1923
  mouthWiden: 1.2,
1911
1924
  brow: 0.15,
1912
1925
  breath: 0.03,
1913
- sway: 0.08
1926
+ sway: 0.08,
1927
+ blushRest: 0.4
1914
1928
  };
1915
1929
  function deriveProfile(v) {
1916
1930
  const farLead = v.lead * (1 - v.fore / 2);
@@ -1997,6 +2011,8 @@ function deriveProfile(v) {
1997
2011
  breathHead: v.breath * 2 / 3,
1998
2012
  /** Hair tip travel at full sway (±20), hh. */
1999
2013
  sway: v.sway,
2014
+ /** The blush's opacity at Cheek 0; at Cheek 1 it is as drawn. */
2015
+ blushRest: v.blushRest,
2000
2016
  /** The neck under the face reaches this far above the jaw, hh — what the
2001
2017
  * chin uncovers as it slides off it: half again the chin's full-turn
2002
2018
  * slide, to the next 0.05. */
@@ -2116,10 +2132,161 @@ function parseLayerRoles(fileNames) {
2116
2132
  // src/auto-rig/generate.ts
2117
2133
  import {
2118
2134
  IKI_FORMAT_VERSION as IKI_FORMAT_VERSION2,
2119
- StandardParameter as P3,
2135
+ StandardParameter as P4,
2120
2136
  parseIkiModel as parseIkiModel3
2121
2137
  } from "@ikijs/format";
2122
2138
 
2139
+ // src/auto-rig/animations.ts
2140
+ import {
2141
+ StandardParameter as P
2142
+ } from "@ikijs/format";
2143
+ function browsY(y) {
2144
+ return [
2145
+ { parameter: P.BrowLeftY, value: y, blend: "add" },
2146
+ { parameter: P.BrowRightY, value: y, blend: "add" }
2147
+ ];
2148
+ }
2149
+ function browsTilt(innerDown) {
2150
+ return [
2151
+ { parameter: P.BrowLeftAngle, value: innerDown, blend: "add" },
2152
+ { parameter: P.BrowRightAngle, value: -innerDown, blend: "add" }
2153
+ ];
2154
+ }
2155
+ function cheek(value) {
2156
+ return { parameter: P.Cheek, value, blend: "add" };
2157
+ }
2158
+ var BROWS = [P.BrowLeftY, P.BrowRightY, P.BrowLeftAngle, P.BrowRightAngle];
2159
+ var EXPRESSIONS = [
2160
+ {
2161
+ id: "smile",
2162
+ description: "Smiling and pleased: warm, friendly, content. For greetings, thanks, agreement and gentle happiness.",
2163
+ parameters: [
2164
+ { parameter: P.MouthForm, value: 1, blend: "add" },
2165
+ ...browsY(0.25),
2166
+ cheek(0.4)
2167
+ ]
2168
+ },
2169
+ {
2170
+ id: "laugh",
2171
+ description: "Laughing, eyes shut and mouth open: delighted, amused. For jokes, playfulness and big joy.",
2172
+ parameters: [
2173
+ { parameter: P.EyeOpenLeft, value: 0, blend: "multiply" },
2174
+ { parameter: P.EyeOpenRight, value: 0, blend: "multiply" },
2175
+ { parameter: P.MouthForm, value: 1, blend: "add" },
2176
+ { parameter: P.MouthOpen, value: 0.8, blend: "overwrite" },
2177
+ ...browsY(0.4),
2178
+ cheek(0.65)
2179
+ ]
2180
+ },
2181
+ {
2182
+ id: "angry",
2183
+ description: "Angry or annoyed, frowning. For irritation, frustration, indignation or scolding.",
2184
+ needs: BROWS,
2185
+ parameters: [
2186
+ ...browsY(-0.5),
2187
+ ...browsTilt(0.8),
2188
+ { parameter: P.MouthForm, value: -0.9, blend: "add" }
2189
+ ]
2190
+ },
2191
+ {
2192
+ id: "sad",
2193
+ description: "Sad or disappointed, downcast. For sorrow, regret, apology or sympathy.",
2194
+ needs: BROWS,
2195
+ parameters: [
2196
+ ...browsY(0.35),
2197
+ ...browsTilt(-0.8),
2198
+ { parameter: P.MouthForm, value: -0.8, blend: "add" }
2199
+ ]
2200
+ },
2201
+ {
2202
+ id: "surprised",
2203
+ description: "Surprised, mouth dropped open: startled, amazed. For shock, sudden news or disbelief.",
2204
+ parameters: [
2205
+ ...browsY(0.8),
2206
+ { parameter: P.MouthOpen, value: 0.9, blend: "overwrite" }
2207
+ ]
2208
+ },
2209
+ {
2210
+ id: "shy",
2211
+ description: "Shy or embarrassed: bashful, flustered. For being praised, teased or caught off guard.",
2212
+ needs: [P.Cheek],
2213
+ parameters: [
2214
+ cheek(1),
2215
+ // Gaze down.
2216
+ { parameter: P.EyeballY, value: -0.65, blend: "add" },
2217
+ { parameter: P.MouthForm, value: 0.4, blend: "add" },
2218
+ ...browsTilt(-0.25)
2219
+ ]
2220
+ }
2221
+ ];
2222
+ function defaultExpressions(declared) {
2223
+ return EXPRESSIONS.filter(
2224
+ (e) => e.needs === void 0 || e.needs.some((id) => declared.has(id))
2225
+ ).map(({ needs: _needs, ...e }) => ({
2226
+ ...e,
2227
+ parameters: e.parameters.filter((t) => declared.has(t.parameter))
2228
+ }));
2229
+ }
2230
+ var DEFAULT_MOTIONS = {
2231
+ Nod: [
2232
+ {
2233
+ description: "Nods yes: agreement, acknowledgement, understanding.",
2234
+ duration: 1,
2235
+ curves: [
2236
+ {
2237
+ parameter: P.AngleY,
2238
+ keys: [
2239
+ [0, 0],
2240
+ [0.3, -18],
2241
+ [0.6, 4],
2242
+ [0.8, -1],
2243
+ [1, 0]
2244
+ ]
2245
+ }
2246
+ ]
2247
+ }
2248
+ ],
2249
+ Shake: [
2250
+ {
2251
+ description: "Shakes the head no: disagreement, refusal, disbelief.",
2252
+ duration: 1.2,
2253
+ // The first swing peaks at 0.2 s, which the default fade would still
2254
+ // be damping.
2255
+ fadeIn: 0.15,
2256
+ curves: [
2257
+ {
2258
+ parameter: P.AngleX,
2259
+ keys: [
2260
+ [0, 0],
2261
+ [0.2, -18],
2262
+ [0.45, 18],
2263
+ [0.7, -16],
2264
+ [0.95, 10],
2265
+ [1.2, 0]
2266
+ ]
2267
+ }
2268
+ ]
2269
+ }
2270
+ ],
2271
+ Tilt: [
2272
+ {
2273
+ description: "Tilts the head to one side and back: curiosity, puzzlement, a question.",
2274
+ duration: 1.4,
2275
+ curves: [
2276
+ {
2277
+ parameter: P.AngleZ,
2278
+ keys: [
2279
+ [0, 0],
2280
+ [0.35, 20],
2281
+ [1, 20],
2282
+ [1.4, 0]
2283
+ ]
2284
+ }
2285
+ ]
2286
+ }
2287
+ ]
2288
+ };
2289
+
2123
2290
  // src/auto-rig/layout.ts
2124
2291
  function boxOfLayer(layer) {
2125
2292
  return boxOfPixels(layer.bbox, layer.canvasW, layer.canvasH);
@@ -2413,7 +2580,7 @@ function checkStrandEdges(strand, irises, hair) {
2413
2580
  }
2414
2581
 
2415
2582
  // src/auto-rig/context.ts
2416
- import { StandardParameter as P } from "@ikijs/format";
2583
+ import { StandardParameter as P2 } from "@ikijs/format";
2417
2584
 
2418
2585
  // src/auto-rig/grid.ts
2419
2586
  var X_STOPS = [-30, 0, 30];
@@ -3629,7 +3796,7 @@ function renderedLander(turn, parts, grids, from = /* @__PURE__ */ new Map()) {
3629
3796
  const family = roleSpec(role).family;
3630
3797
  const part = parts.find((p) => p.id === role);
3631
3798
  const g = grids.get(family);
3632
- const warp = part?.warps?.find((w) => w.parameter === P.AngleX);
3799
+ const warp = part?.warps?.find((w) => w.parameter === P2.AngleX);
3633
3800
  if (part === void 0 || part.mesh === void 0 || !g && !warp) {
3634
3801
  return x + field(role)(x, y, ax, 0)[0];
3635
3802
  }
@@ -4340,7 +4507,7 @@ function buildTurn(frame, landmarks, q) {
4340
4507
 
4341
4508
  // src/auto-rig/drivers.ts
4342
4509
  import {
4343
- StandardParameter as P2
4510
+ StandardParameter as P3
4344
4511
  } from "@ikijs/format";
4345
4512
  var SWAY_RANGE = 20;
4346
4513
  var SWAY_CURVE = 1.4;
@@ -4419,13 +4586,13 @@ function gazeBindings(irisW, share = 1) {
4419
4586
  const gy = AMPLITUDE.gazeY * irisW * share;
4420
4587
  return [
4421
4588
  {
4422
- parameter: P2.EyeballX,
4589
+ parameter: P3.EyeballX,
4423
4590
  channel: "translateX",
4424
4591
  from: -round1(gx),
4425
4592
  to: round1(gx)
4426
4593
  },
4427
4594
  {
4428
- parameter: P2.EyeballY,
4595
+ parameter: P3.EyeballY,
4429
4596
  channel: "translateY",
4430
4597
  from: -round1(gy),
4431
4598
  to: round1(gy)
@@ -4439,8 +4606,8 @@ function browReach(hh) {
4439
4606
  return AMPLITUDE.brow * hh;
4440
4607
  }
4441
4608
  function browBindings(side, reach) {
4442
- const y = side === "L" ? P2.BrowLeftY : P2.BrowRightY;
4443
- const a = side === "L" ? P2.BrowLeftAngle : P2.BrowRightAngle;
4609
+ const y = side === "L" ? P3.BrowLeftY : P3.BrowRightY;
4610
+ const a = side === "L" ? P3.BrowLeftAngle : P3.BrowRightAngle;
4444
4611
  return [
4445
4612
  {
4446
4613
  parameter: y,
@@ -4456,7 +4623,7 @@ function mouthForm(mesh, box) {
4456
4623
  const a = 0.3 * bh(box);
4457
4624
  const c = cx(box);
4458
4625
  const half = bw(box) / 2;
4459
- return localWarp(P2.MouthForm, mesh, box, [-1, 0, 1], ([x], v) => {
4626
+ return localWarp(P3.MouthForm, mesh, box, [-1, 0, 1], ([x], v) => {
4460
4627
  if (v === 0) return [0, 0];
4461
4628
  const u = (x - c) / half;
4462
4629
  const widen = v > 0 ? 0.05 : 0.03;
@@ -4466,7 +4633,7 @@ function mouthForm(mesh, box) {
4466
4633
  function mouthOpenGrow(mesh, box) {
4467
4634
  const c = cx(box);
4468
4635
  return localWarp(
4469
- P2.MouthOpen,
4636
+ P3.MouthOpen,
4470
4637
  mesh,
4471
4638
  box,
4472
4639
  [0, 1],
@@ -4475,7 +4642,7 @@ function mouthOpenGrow(mesh, box) {
4475
4642
  }
4476
4643
  function mouthWiden(mesh, box) {
4477
4644
  const c = cx(box);
4478
- return localWarp(P2.MouthOpen, mesh, box, [0, 1], ([x], v) => [
4645
+ return localWarp(P3.MouthOpen, mesh, box, [0, 1], ([x], v) => [
4479
4646
  v * (AMPLITUDE.mouthOpenWidth - 1) * (x - c),
4480
4647
  0
4481
4648
  ]);
@@ -4493,7 +4660,7 @@ function hairSway(param, mesh, box, amp) {
4493
4660
  );
4494
4661
  }
4495
4662
  function tiltHang(mesh, box, pivot, weight) {
4496
- return localWarp(P2.AngleZ, mesh, box, Z_STOPS, ([x, y], v, i) => {
4663
+ return localWarp(P3.AngleZ, mesh, box, Z_STOPS, ([x, y], v, i) => {
4497
4664
  const g = weight(y, i);
4498
4665
  if (g === 0 || v === 0) return [0, 0];
4499
4666
  const a = -g * rollOf(v);
@@ -4646,14 +4813,17 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4646
4813
  ...part.warps ?? []
4647
4814
  ];
4648
4815
  }
4816
+ const parameters = declareParameters(new Set(byRole.keys()));
4649
4817
  const model = {
4650
4818
  version: IKI_FORMAT_VERSION2,
4651
4819
  name: "auto-rigged",
4652
4820
  canvas: { width: canvas.width, height: canvas.height },
4653
- parameters: declareParameters(new Set(byRole.keys())),
4821
+ parameters,
4654
4822
  parts,
4655
4823
  deformers: deformers(frame, grids, has("body") ? box("body") : void 0),
4656
- ...has("hair_front") ? { physics: hairPhysics() } : {}
4824
+ ...has("hair_front") ? { physics: hairPhysics() } : {},
4825
+ expressions: defaultExpressions(new Set(parameters.map((p) => p.id))),
4826
+ motions: DEFAULT_MOTIONS
4657
4827
  };
4658
4828
  const valid = parseIkiModel3(model);
4659
4829
  if (hasNose && options.onTurnSolved) {
@@ -4670,14 +4840,14 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4670
4840
  function turnWarps(mesh, b, fieldOf) {
4671
4841
  return [
4672
4842
  localWarp(
4673
- P3.AngleX,
4843
+ P4.AngleX,
4674
4844
  mesh,
4675
4845
  b,
4676
4846
  X_STOPS,
4677
4847
  ([x, y], v, i) => v === 0 ? [0, 0] : [fieldOf(i)(x, y, v, 0)[0], 0]
4678
4848
  ),
4679
4849
  localWarp(
4680
- P3.AngleY,
4850
+ P4.AngleY,
4681
4851
  mesh,
4682
4852
  b,
4683
4853
  Y_STOPS,
@@ -4725,7 +4895,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
4725
4895
  }
4726
4896
  if (mesh !== void 0) part.mesh = mesh;
4727
4897
  const side = spec.role.endsWith("_L") ? "L" : "R";
4728
- const eyeOpen = side === "L" ? P3.EyeOpenLeft : P3.EyeOpenRight;
4898
+ const eyeOpen = side === "L" ? P4.EyeOpenLeft : P4.EyeOpenRight;
4729
4899
  const hh = frame.hh;
4730
4900
  switch (spec.role) {
4731
4901
  case "face":
@@ -4734,7 +4904,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
4734
4904
  warps.push(
4735
4905
  tiltHang(mesh, b, pivot, (_y, i) => isNeck[i] ? 1 : 0),
4736
4906
  localWarp(
4737
- P3.Breath,
4907
+ P4.Breath,
4738
4908
  mesh,
4739
4909
  b,
4740
4910
  [0, 1],
@@ -4781,8 +4951,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
4781
4951
  if (byRole.has("mouth_open")) {
4782
4952
  warps.push(mouthWiden(mesh, b));
4783
4953
  bindings.push(
4784
- { parameter: P3.MouthOpen, channel: "opacity", from: 1, to: 0 },
4785
- { parameter: P3.MouthOpen, channel: "opacity", from: 1, to: 0 }
4954
+ { parameter: P4.MouthOpen, channel: "opacity", from: 1, to: 0 },
4955
+ { parameter: P4.MouthOpen, channel: "opacity", from: 1, to: 0 }
4786
4956
  );
4787
4957
  extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
4788
4958
  } else {
@@ -4792,19 +4962,19 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
4792
4962
  );
4793
4963
  bindings.push(
4794
4964
  {
4795
- parameter: P3.MouthOpen,
4965
+ parameter: P4.MouthOpen,
4796
4966
  channel: "scaleY",
4797
4967
  from: 0,
4798
4968
  to: roundTo(grow, 0.01)
4799
4969
  },
4800
4970
  {
4801
- parameter: P3.MouthOpen,
4971
+ parameter: P4.MouthOpen,
4802
4972
  channel: "scaleX",
4803
4973
  from: 0,
4804
4974
  to: roundTo(AMPLITUDE.mouthOpenWidth - 1, 0.01)
4805
4975
  },
4806
4976
  {
4807
- parameter: P3.MouthOpen,
4977
+ parameter: P4.MouthOpen,
4808
4978
  channel: "translateY",
4809
4979
  from: 0,
4810
4980
  to: -roundTo(grow * bh(b) / 2, 0.01)
@@ -4816,20 +4986,29 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
4816
4986
  case "mouth_open":
4817
4987
  warps.push(mouthForm(mesh, b), mouthOpenGrow(mesh, b));
4818
4988
  bindings.push({
4819
- parameter: P3.MouthOpen,
4989
+ parameter: P4.MouthOpen,
4820
4990
  channel: "opacity",
4821
4991
  from: 0,
4822
4992
  to: 1
4823
4993
  });
4824
4994
  extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
4825
4995
  break;
4996
+ case "blush_L":
4997
+ case "blush_R":
4998
+ bindings.push({
4999
+ parameter: P4.Cheek,
5000
+ channel: "opacity",
5001
+ from: AMPLITUDE.blushRest,
5002
+ to: 1
5003
+ });
5004
+ break;
4826
5005
  case "hair_front":
4827
5006
  case "hair_back":
4828
5007
  if (byRole.has("hair_front")) {
4829
5008
  const amp = style.sway * AMPLITUDE.sway * hh;
4830
5009
  warps.push(
4831
- hairSway(P3.HairSwayX, mesh, b, amp),
4832
- hairSway(P3.HairSwayZ, mesh, b, amp)
5010
+ hairSway(P4.HairSwayX, mesh, b, amp),
5011
+ hairSway(P4.HairSwayZ, mesh, b, amp)
4833
5012
  );
4834
5013
  }
4835
5014
  warps.push(tiltHang(mesh, b, pivot, hairHangWeight(frame.chinY, b.y0)));
@@ -4910,7 +5089,7 @@ function deformers(frame, grids, body) {
4910
5089
  pivot: { x: roundTo(cx(body), 0.01), y: roundTo(body.y1, 0.01) },
4911
5090
  bindings: [
4912
5091
  {
4913
- parameter: P3.Breath,
5092
+ parameter: P4.Breath,
4914
5093
  channel: "translateY",
4915
5094
  from: 0,
4916
5095
  to: roundTo(AMPLITUDE.breathBody * frame.hh, 0.01)
@@ -4923,9 +5102,9 @@ function deformers(frame, grids, body) {
4923
5102
  pivot: { x: roundTo(frame.axisX, 0.01), y: roundTo(frame.chinY, 0.01) },
4924
5103
  bindings: [
4925
5104
  // AngleZ is clockwise-positive (Live2D's); a rotation is CCW-positive.
4926
- { parameter: P3.AngleZ, channel: "rotate", from: ROLL_DEG, to: -ROLL_DEG },
5105
+ { parameter: P4.AngleZ, channel: "rotate", from: ROLL_DEG, to: -ROLL_DEG },
4927
5106
  {
4928
- parameter: P3.Breath,
5107
+ parameter: P4.Breath,
4929
5108
  channel: "translateY",
4930
5109
  from: 0,
4931
5110
  to: roundTo(AMPLITUDE.breathHead * frame.hh, 0.01)
@@ -4943,8 +5122,8 @@ function deformers(frame, grids, body) {
4943
5122
  points: g.lattice.points
4944
5123
  },
4945
5124
  warp2d: {
4946
- parameter: P3.AngleX,
4947
- parameterY: P3.AngleY,
5125
+ parameter: P4.AngleX,
5126
+ parameterY: P4.AngleY,
4948
5127
  valuesX: X_STOPS,
4949
5128
  valuesY: Y_STOPS,
4950
5129
  keyforms2d: g.keyforms.map((offsets) => ({ offsets }))
@@ -4957,16 +5136,16 @@ function hairPhysics() {
4957
5136
  return [
4958
5137
  {
4959
5138
  id: "hairSway",
4960
- input: { parameter: P3.AngleX, weight: 1 },
4961
- output: { parameter: P3.HairSwayX, scale: 5 },
5139
+ input: { parameter: P4.AngleX, weight: 1 },
5140
+ output: { parameter: P4.HairSwayX, scale: 5 },
4962
5141
  mass: 1,
4963
5142
  stiffness: 30,
4964
5143
  damping: 3
4965
5144
  },
4966
5145
  {
4967
5146
  id: "hairTilt",
4968
- input: { parameter: P3.AngleZ, weight: 1 },
4969
- output: { parameter: P3.HairSwayZ, scale: 8 },
5147
+ input: { parameter: P4.AngleZ, weight: 1 },
5148
+ output: { parameter: P4.HairSwayZ, scale: 8 },
4970
5149
  mass: 1,
4971
5150
  stiffness: 25,
4972
5151
  damping: 2.5
@@ -4976,29 +5155,32 @@ function hairPhysics() {
4976
5155
  function declareParameters(roles) {
4977
5156
  const out = [];
4978
5157
  const add = (id, name, min, max, def) => out.push({ id, name, min, max, default: def });
4979
- add(P3.AngleX, "Head Angle", -30, 30, 0);
4980
- add(P3.AngleY, "Head Angle Y", -30, 30, 0);
4981
- add(P3.AngleZ, "Head Angle Z", -30, 30, 0);
4982
- add(P3.EyeOpenLeft, "Eye L", 0, 1, 1);
4983
- add(P3.EyeOpenRight, "Eye R", 0, 1, 1);
5158
+ add(P4.AngleX, "Head Angle", -30, 30, 0);
5159
+ add(P4.AngleY, "Head Angle Y", -30, 30, 0);
5160
+ add(P4.AngleZ, "Head Angle Z", -30, 30, 0);
5161
+ add(P4.EyeOpenLeft, "Eye L", 0, 1, 1);
5162
+ add(P4.EyeOpenRight, "Eye R", 0, 1, 1);
4984
5163
  if ([...roles].some((r) => /^(iris|pupil|highlight)_/.test(r))) {
4985
- add(P3.EyeballX, "Gaze X", -1, 1, 0);
4986
- add(P3.EyeballY, "Gaze Y", -1, 1, 0);
5164
+ add(P4.EyeballX, "Gaze X", -1, 1, 0);
5165
+ add(P4.EyeballY, "Gaze Y", -1, 1, 0);
4987
5166
  }
4988
- add(P3.MouthOpen, "Mouth Open", 0, 1, 0);
4989
- add(P3.MouthForm, "Mouth Form", -1, 1, 0);
5167
+ add(P4.MouthOpen, "Mouth Open", 0, 1, 0);
5168
+ add(P4.MouthForm, "Mouth Form", -1, 1, 0);
4990
5169
  if (roles.has("brow_L")) {
4991
- add(P3.BrowLeftY, "Brow L Y", -1, 1, 0);
4992
- add(P3.BrowLeftAngle, "Brow L Angle", -1, 1, 0);
5170
+ add(P4.BrowLeftY, "Brow L Y", -1, 1, 0);
5171
+ add(P4.BrowLeftAngle, "Brow L Angle", -1, 1, 0);
4993
5172
  }
4994
5173
  if (roles.has("brow_R")) {
4995
- add(P3.BrowRightY, "Brow R Y", -1, 1, 0);
4996
- add(P3.BrowRightAngle, "Brow R Angle", -1, 1, 0);
5174
+ add(P4.BrowRightY, "Brow R Y", -1, 1, 0);
5175
+ add(P4.BrowRightAngle, "Brow R Angle", -1, 1, 0);
5176
+ }
5177
+ if (roles.has("blush_L") || roles.has("blush_R")) {
5178
+ add(P4.Cheek, "Cheek", 0, 1, 0);
4997
5179
  }
4998
- add(P3.Breath, "Breath", 0, 1, 0);
5180
+ add(P4.Breath, "Breath", 0, 1, 0);
4999
5181
  if (roles.has("hair_front")) {
5000
- add(P3.HairSwayX, "Hair Sway X", -SWAY_RANGE, SWAY_RANGE, 0);
5001
- add(P3.HairSwayZ, "Hair Sway Z", -SWAY_RANGE, SWAY_RANGE, 0);
5182
+ add(P4.HairSwayX, "Hair Sway X", -SWAY_RANGE, SWAY_RANGE, 0);
5183
+ add(P4.HairSwayZ, "Hair Sway Z", -SWAY_RANGE, SWAY_RANGE, 0);
5002
5184
  }
5003
5185
  return out;
5004
5186
  }