@ikijs/editor 0.14.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. */
@@ -2116,10 +2119,161 @@ function parseLayerRoles(fileNames) {
2116
2119
  // src/auto-rig/generate.ts
2117
2120
  import {
2118
2121
  IKI_FORMAT_VERSION as IKI_FORMAT_VERSION2,
2119
- StandardParameter as P3,
2122
+ StandardParameter as P4,
2120
2123
  parseIkiModel as parseIkiModel3
2121
2124
  } from "@ikijs/format";
2122
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
+
2123
2277
  // src/auto-rig/layout.ts
2124
2278
  function boxOfLayer(layer) {
2125
2279
  return boxOfPixels(layer.bbox, layer.canvasW, layer.canvasH);
@@ -2413,7 +2567,7 @@ function checkStrandEdges(strand, irises, hair) {
2413
2567
  }
2414
2568
 
2415
2569
  // src/auto-rig/context.ts
2416
- import { StandardParameter as P } from "@ikijs/format";
2570
+ import { StandardParameter as P2 } from "@ikijs/format";
2417
2571
 
2418
2572
  // src/auto-rig/grid.ts
2419
2573
  var X_STOPS = [-30, 0, 30];
@@ -3629,7 +3783,7 @@ function renderedLander(turn, parts, grids, from = /* @__PURE__ */ new Map()) {
3629
3783
  const family = roleSpec(role).family;
3630
3784
  const part = parts.find((p) => p.id === role);
3631
3785
  const g = grids.get(family);
3632
- const warp = part?.warps?.find((w) => w.parameter === P.AngleX);
3786
+ const warp = part?.warps?.find((w) => w.parameter === P2.AngleX);
3633
3787
  if (part === void 0 || part.mesh === void 0 || !g && !warp) {
3634
3788
  return x + field(role)(x, y, ax, 0)[0];
3635
3789
  }
@@ -4340,7 +4494,7 @@ function buildTurn(frame, landmarks, q) {
4340
4494
 
4341
4495
  // src/auto-rig/drivers.ts
4342
4496
  import {
4343
- StandardParameter as P2
4497
+ StandardParameter as P3
4344
4498
  } from "@ikijs/format";
4345
4499
  var SWAY_RANGE = 20;
4346
4500
  var SWAY_CURVE = 1.4;
@@ -4419,13 +4573,13 @@ function gazeBindings(irisW, share = 1) {
4419
4573
  const gy = AMPLITUDE.gazeY * irisW * share;
4420
4574
  return [
4421
4575
  {
4422
- parameter: P2.EyeballX,
4576
+ parameter: P3.EyeballX,
4423
4577
  channel: "translateX",
4424
4578
  from: -round1(gx),
4425
4579
  to: round1(gx)
4426
4580
  },
4427
4581
  {
4428
- parameter: P2.EyeballY,
4582
+ parameter: P3.EyeballY,
4429
4583
  channel: "translateY",
4430
4584
  from: -round1(gy),
4431
4585
  to: round1(gy)
@@ -4439,8 +4593,8 @@ function browReach(hh) {
4439
4593
  return AMPLITUDE.brow * hh;
4440
4594
  }
4441
4595
  function browBindings(side, reach) {
4442
- const y = side === "L" ? P2.BrowLeftY : P2.BrowRightY;
4443
- 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;
4444
4598
  return [
4445
4599
  {
4446
4600
  parameter: y,
@@ -4456,7 +4610,7 @@ function mouthForm(mesh, box) {
4456
4610
  const a = 0.3 * bh(box);
4457
4611
  const c = cx(box);
4458
4612
  const half = bw(box) / 2;
4459
- return localWarp(P2.MouthForm, mesh, box, [-1, 0, 1], ([x], v) => {
4613
+ return localWarp(P3.MouthForm, mesh, box, [-1, 0, 1], ([x], v) => {
4460
4614
  if (v === 0) return [0, 0];
4461
4615
  const u = (x - c) / half;
4462
4616
  const widen = v > 0 ? 0.05 : 0.03;
@@ -4466,7 +4620,7 @@ function mouthForm(mesh, box) {
4466
4620
  function mouthOpenGrow(mesh, box) {
4467
4621
  const c = cx(box);
4468
4622
  return localWarp(
4469
- P2.MouthOpen,
4623
+ P3.MouthOpen,
4470
4624
  mesh,
4471
4625
  box,
4472
4626
  [0, 1],
@@ -4475,7 +4629,7 @@ function mouthOpenGrow(mesh, box) {
4475
4629
  }
4476
4630
  function mouthWiden(mesh, box) {
4477
4631
  const c = cx(box);
4478
- return localWarp(P2.MouthOpen, mesh, box, [0, 1], ([x], v) => [
4632
+ return localWarp(P3.MouthOpen, mesh, box, [0, 1], ([x], v) => [
4479
4633
  v * (AMPLITUDE.mouthOpenWidth - 1) * (x - c),
4480
4634
  0
4481
4635
  ]);
@@ -4493,7 +4647,7 @@ function hairSway(param, mesh, box, amp) {
4493
4647
  );
4494
4648
  }
4495
4649
  function tiltHang(mesh, box, pivot, weight) {
4496
- 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) => {
4497
4651
  const g = weight(y, i);
4498
4652
  if (g === 0 || v === 0) return [0, 0];
4499
4653
  const a = -g * rollOf(v);
@@ -4646,14 +4800,17 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4646
4800
  ...part.warps ?? []
4647
4801
  ];
4648
4802
  }
4803
+ const parameters = declareParameters(new Set(byRole.keys()));
4649
4804
  const model = {
4650
4805
  version: IKI_FORMAT_VERSION2,
4651
4806
  name: "auto-rigged",
4652
4807
  canvas: { width: canvas.width, height: canvas.height },
4653
- parameters: declareParameters(new Set(byRole.keys())),
4808
+ parameters,
4654
4809
  parts,
4655
4810
  deformers: deformers(frame, grids, has("body") ? box("body") : void 0),
4656
- ...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
4657
4814
  };
4658
4815
  const valid = parseIkiModel3(model);
4659
4816
  if (hasNose && options.onTurnSolved) {
@@ -4670,14 +4827,14 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4670
4827
  function turnWarps(mesh, b, fieldOf) {
4671
4828
  return [
4672
4829
  localWarp(
4673
- P3.AngleX,
4830
+ P4.AngleX,
4674
4831
  mesh,
4675
4832
  b,
4676
4833
  X_STOPS,
4677
4834
  ([x, y], v, i) => v === 0 ? [0, 0] : [fieldOf(i)(x, y, v, 0)[0], 0]
4678
4835
  ),
4679
4836
  localWarp(
4680
- P3.AngleY,
4837
+ P4.AngleY,
4681
4838
  mesh,
4682
4839
  b,
4683
4840
  Y_STOPS,
@@ -4725,7 +4882,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
4725
4882
  }
4726
4883
  if (mesh !== void 0) part.mesh = mesh;
4727
4884
  const side = spec.role.endsWith("_L") ? "L" : "R";
4728
- const eyeOpen = side === "L" ? P3.EyeOpenLeft : P3.EyeOpenRight;
4885
+ const eyeOpen = side === "L" ? P4.EyeOpenLeft : P4.EyeOpenRight;
4729
4886
  const hh = frame.hh;
4730
4887
  switch (spec.role) {
4731
4888
  case "face":
@@ -4734,7 +4891,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
4734
4891
  warps.push(
4735
4892
  tiltHang(mesh, b, pivot, (_y, i) => isNeck[i] ? 1 : 0),
4736
4893
  localWarp(
4737
- P3.Breath,
4894
+ P4.Breath,
4738
4895
  mesh,
4739
4896
  b,
4740
4897
  [0, 1],
@@ -4781,8 +4938,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
4781
4938
  if (byRole.has("mouth_open")) {
4782
4939
  warps.push(mouthWiden(mesh, b));
4783
4940
  bindings.push(
4784
- { parameter: P3.MouthOpen, channel: "opacity", from: 1, to: 0 },
4785
- { 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 }
4786
4943
  );
4787
4944
  extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
4788
4945
  } else {
@@ -4792,19 +4949,19 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
4792
4949
  );
4793
4950
  bindings.push(
4794
4951
  {
4795
- parameter: P3.MouthOpen,
4952
+ parameter: P4.MouthOpen,
4796
4953
  channel: "scaleY",
4797
4954
  from: 0,
4798
4955
  to: roundTo(grow, 0.01)
4799
4956
  },
4800
4957
  {
4801
- parameter: P3.MouthOpen,
4958
+ parameter: P4.MouthOpen,
4802
4959
  channel: "scaleX",
4803
4960
  from: 0,
4804
4961
  to: roundTo(AMPLITUDE.mouthOpenWidth - 1, 0.01)
4805
4962
  },
4806
4963
  {
4807
- parameter: P3.MouthOpen,
4964
+ parameter: P4.MouthOpen,
4808
4965
  channel: "translateY",
4809
4966
  from: 0,
4810
4967
  to: -roundTo(grow * bh(b) / 2, 0.01)
@@ -4816,20 +4973,29 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
4816
4973
  case "mouth_open":
4817
4974
  warps.push(mouthForm(mesh, b), mouthOpenGrow(mesh, b));
4818
4975
  bindings.push({
4819
- parameter: P3.MouthOpen,
4976
+ parameter: P4.MouthOpen,
4820
4977
  channel: "opacity",
4821
4978
  from: 0,
4822
4979
  to: 1
4823
4980
  });
4824
4981
  extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
4825
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;
4826
4992
  case "hair_front":
4827
4993
  case "hair_back":
4828
4994
  if (byRole.has("hair_front")) {
4829
4995
  const amp = style.sway * AMPLITUDE.sway * hh;
4830
4996
  warps.push(
4831
- hairSway(P3.HairSwayX, mesh, b, amp),
4832
- hairSway(P3.HairSwayZ, mesh, b, amp)
4997
+ hairSway(P4.HairSwayX, mesh, b, amp),
4998
+ hairSway(P4.HairSwayZ, mesh, b, amp)
4833
4999
  );
4834
5000
  }
4835
5001
  warps.push(tiltHang(mesh, b, pivot, hairHangWeight(frame.chinY, b.y0)));
@@ -4910,7 +5076,7 @@ function deformers(frame, grids, body) {
4910
5076
  pivot: { x: roundTo(cx(body), 0.01), y: roundTo(body.y1, 0.01) },
4911
5077
  bindings: [
4912
5078
  {
4913
- parameter: P3.Breath,
5079
+ parameter: P4.Breath,
4914
5080
  channel: "translateY",
4915
5081
  from: 0,
4916
5082
  to: roundTo(AMPLITUDE.breathBody * frame.hh, 0.01)
@@ -4923,9 +5089,9 @@ function deformers(frame, grids, body) {
4923
5089
  pivot: { x: roundTo(frame.axisX, 0.01), y: roundTo(frame.chinY, 0.01) },
4924
5090
  bindings: [
4925
5091
  // AngleZ is clockwise-positive (Live2D's); a rotation is CCW-positive.
4926
- { parameter: P3.AngleZ, channel: "rotate", from: ROLL_DEG, to: -ROLL_DEG },
5092
+ { parameter: P4.AngleZ, channel: "rotate", from: ROLL_DEG, to: -ROLL_DEG },
4927
5093
  {
4928
- parameter: P3.Breath,
5094
+ parameter: P4.Breath,
4929
5095
  channel: "translateY",
4930
5096
  from: 0,
4931
5097
  to: roundTo(AMPLITUDE.breathHead * frame.hh, 0.01)
@@ -4943,8 +5109,8 @@ function deformers(frame, grids, body) {
4943
5109
  points: g.lattice.points
4944
5110
  },
4945
5111
  warp2d: {
4946
- parameter: P3.AngleX,
4947
- parameterY: P3.AngleY,
5112
+ parameter: P4.AngleX,
5113
+ parameterY: P4.AngleY,
4948
5114
  valuesX: X_STOPS,
4949
5115
  valuesY: Y_STOPS,
4950
5116
  keyforms2d: g.keyforms.map((offsets) => ({ offsets }))
@@ -4957,16 +5123,16 @@ function hairPhysics() {
4957
5123
  return [
4958
5124
  {
4959
5125
  id: "hairSway",
4960
- input: { parameter: P3.AngleX, weight: 1 },
4961
- output: { parameter: P3.HairSwayX, scale: 5 },
5126
+ input: { parameter: P4.AngleX, weight: 1 },
5127
+ output: { parameter: P4.HairSwayX, scale: 5 },
4962
5128
  mass: 1,
4963
5129
  stiffness: 30,
4964
5130
  damping: 3
4965
5131
  },
4966
5132
  {
4967
5133
  id: "hairTilt",
4968
- input: { parameter: P3.AngleZ, weight: 1 },
4969
- output: { parameter: P3.HairSwayZ, scale: 8 },
5134
+ input: { parameter: P4.AngleZ, weight: 1 },
5135
+ output: { parameter: P4.HairSwayZ, scale: 8 },
4970
5136
  mass: 1,
4971
5137
  stiffness: 25,
4972
5138
  damping: 2.5
@@ -4976,29 +5142,32 @@ function hairPhysics() {
4976
5142
  function declareParameters(roles) {
4977
5143
  const out = [];
4978
5144
  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);
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);
4984
5150
  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);
5151
+ add(P4.EyeballX, "Gaze X", -1, 1, 0);
5152
+ add(P4.EyeballY, "Gaze Y", -1, 1, 0);
4987
5153
  }
4988
- add(P3.MouthOpen, "Mouth Open", 0, 1, 0);
4989
- 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);
4990
5156
  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);
5157
+ add(P4.BrowLeftY, "Brow L Y", -1, 1, 0);
5158
+ add(P4.BrowLeftAngle, "Brow L Angle", -1, 1, 0);
4993
5159
  }
4994
5160
  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);
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);
4997
5166
  }
4998
- add(P3.Breath, "Breath", 0, 1, 0);
5167
+ add(P4.Breath, "Breath", 0, 1, 0);
4999
5168
  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);
5169
+ add(P4.HairSwayX, "Hair Sway X", -SWAY_RANGE, SWAY_RANGE, 0);
5170
+ add(P4.HairSwayZ, "Hair Sway Z", -SWAY_RANGE, SWAY_RANGE, 0);
5002
5171
  }
5003
5172
  return out;
5004
5173
  }