@ikijs/editor 0.20.0 → 0.21.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
@@ -1567,7 +1567,10 @@ var ROW_RUN_ROLES = /* @__PURE__ */ new Set([
1567
1567
  "arm_L",
1568
1568
  "arm_R",
1569
1569
  "forearm_pose_L",
1570
- "forearm_pose_R"
1570
+ "forearm_pose_R",
1571
+ "mouth_inner",
1572
+ "lip_lower",
1573
+ "lip_upper"
1571
1574
  ]);
1572
1575
  var REST_HIDDEN_ROLES = /* @__PURE__ */ new Set([
1573
1576
  "forearm_pose_L",
@@ -2184,6 +2187,29 @@ function gridMesh(cols, rows) {
2184
2187
  }
2185
2188
  return { vertices, uvs, indices };
2186
2189
  }
2190
+ function columnMesh(b, grid) {
2191
+ const { xs } = grid;
2192
+ const rows = grid.ys.length - 1;
2193
+ const vertices = [];
2194
+ const uvs = [];
2195
+ for (let r = 0; r <= rows; r++) {
2196
+ for (const x of xs) {
2197
+ const u = (x - b.x0) / bw(b);
2198
+ vertices.push(roundTo(u - 0.5, 1e-5), roundTo(0.5 - r / rows, 1e-5));
2199
+ uvs.push(roundTo(u, 1e-5), roundTo(r / rows, 1e-5));
2200
+ }
2201
+ }
2202
+ const n = xs.length;
2203
+ const indices = [];
2204
+ for (let r = 0; r < rows; r++) {
2205
+ for (let c = 0; c + 1 < n; c++) {
2206
+ const tl = r * n + c;
2207
+ const bl = tl + n;
2208
+ indices.push(bl, bl + 1, tl, tl, bl + 1, tl + 1);
2209
+ }
2210
+ }
2211
+ return { vertices, uvs, indices };
2212
+ }
2187
2213
  function meshPoints(mesh, box) {
2188
2214
  const out = [];
2189
2215
  for (let i = 0; i < mesh.vertices.length; i += 2) {
@@ -2309,10 +2335,13 @@ function browBindings(side, reach) {
2309
2335
  ];
2310
2336
  }
2311
2337
  var BROW_TILT = 15;
2312
- function mouthForm(mesh, box) {
2313
- const a = 0.3 * bh(box);
2314
- const c = cx(box);
2315
- const half = bw(box) / 2;
2338
+ var mouthFrameOf = (box) => ({
2339
+ c: cx(box),
2340
+ half: bw(box) / 2,
2341
+ amp: 0.3 * bh(box)
2342
+ });
2343
+ function mouthForm(mesh, box, frame = mouthFrameOf(box)) {
2344
+ const { c, half, amp: a } = frame;
2316
2345
  return localWarp(P.MouthForm, mesh, box, [-1, 0, 1], ([x], v) => {
2317
2346
  if (v === 0) return [0, 0];
2318
2347
  const u = (x - c) / half;
@@ -2330,8 +2359,8 @@ function mouthOpenGrow(mesh, box) {
2330
2359
  ([x, y], v) => v === 1 ? [(AMPLITUDE.mouthOpenWidth - 1) * (x - c), 0] : [0, box.y1 + (y - box.y1) * 0.2 - y]
2331
2360
  );
2332
2361
  }
2333
- function mouthWiden(mesh, box) {
2334
- const c = cx(box);
2362
+ function mouthWiden(mesh, box, frame = mouthFrameOf(box)) {
2363
+ const c = frame.c;
2335
2364
  return localWarp(P.MouthOpen, mesh, box, [0, 1], ([x], v) => [
2336
2365
  v * (AMPLITUDE.mouthOpenWidth - 1) * (x - c),
2337
2366
  0
@@ -2990,6 +3019,8 @@ function forearmPoseDeformer(role, arm) {
2990
3019
  }
2991
3020
 
2992
3021
  // src/auto-rig/roles.ts
3022
+ var LIP_ROLES = ["mouth_inner", "lip_lower", "lip_upper"];
3023
+ var isLipRole = (role) => LIP_ROLES.includes(role);
2993
3024
  var ROLE_TABLE = [
2994
3025
  { role: "hair_back", family: "hair_back" },
2995
3026
  { role: "body", family: "body" },
@@ -2999,8 +3030,14 @@ var ROLE_TABLE = [
2999
3030
  { role: "blush_L", family: "face" },
3000
3031
  { role: "blush_R", family: "face" },
3001
3032
  { role: "nose", family: "nose" },
3002
- { role: "mouth", family: "mouth", required: true },
3033
+ { role: "mouth", family: "mouth" },
3003
3034
  { role: "mouth_open", family: "mouth" },
3035
+ // The lip set, back to front: the interior, the lower lip's skin, the upper
3036
+ // lip and its line over both. It folds open instead of crossfading
3037
+ // (`mouth.ts`), so it stands in for `mouth`, never beside it.
3038
+ { role: "mouth_inner", family: "mouth" },
3039
+ { role: "lip_lower", family: "mouth" },
3040
+ { role: "lip_upper", family: "mouth" },
3004
3041
  { role: "eye_L", family: "eye_L", required: true },
3005
3042
  { role: "eye_R", family: "eye_R", required: true },
3006
3043
  { role: "iris_L", family: "eye_L" },
@@ -3058,6 +3095,29 @@ function checkArmsHaveBody(roles) {
3058
3095
  );
3059
3096
  }
3060
3097
  }
3098
+ function checkMouth(roles) {
3099
+ const has = new Set(roles);
3100
+ const present = LIP_ROLES.filter((r) => has.has(r));
3101
+ if (present.length === 0) {
3102
+ if (!has.has("mouth")) {
3103
+ throw new Error(
3104
+ `auto-rig: missing required role mouth (or the lip set ${LIP_ROLES.join(", ")})`
3105
+ );
3106
+ }
3107
+ return;
3108
+ }
3109
+ if (present.length < LIP_ROLES.length) {
3110
+ throw new Error(
3111
+ `auto-rig: the lip set is partial: missing ${LIP_ROLES.filter((r) => !has.has(r)).join(", ")}`
3112
+ );
3113
+ }
3114
+ const mixed = ["mouth", "mouth_open"].filter((r) => has.has(r));
3115
+ if (mixed.length > 0) {
3116
+ throw new Error(
3117
+ `auto-rig: the lip set cannot be mixed with ${mixed.join(", ")} (the lip set replaces the mouth drawings; use one or the other)`
3118
+ );
3119
+ }
3120
+ }
3061
3121
  function checkPosesHaveArms(roles) {
3062
3122
  const has = new Set(roles);
3063
3123
  for (const side of ["L", "R"]) {
@@ -3093,6 +3153,7 @@ function parseLayerRoles(fileNames) {
3093
3153
  `auto-rig: missing required role${missing.length > 1 ? "s" : ""} ${missing.join(", ")}`
3094
3154
  );
3095
3155
  }
3156
+ checkMouth(seen.keys());
3096
3157
  checkArmsHaveBody(seen.keys());
3097
3158
  checkPosesHaveArms(seen.keys());
3098
3159
  return out;
@@ -3101,7 +3162,7 @@ function parseLayerRoles(fileNames) {
3101
3162
  // src/auto-rig/generate.ts
3102
3163
  import {
3103
3164
  IKI_FORMAT_VERSION as IKI_FORMAT_VERSION2,
3104
- StandardParameter as P7,
3165
+ StandardParameter as P8,
3105
3166
  parseIkiModel as parseIkiModel3
3106
3167
  } from "@ikijs/format";
3107
3168
 
@@ -3408,6 +3469,14 @@ function checkLayers(layers, canvas) {
3408
3469
  if (missing.length > 0) {
3409
3470
  throw new Error(`auto-rig: missing required role(s) ${missing.join(", ")}`);
3410
3471
  }
3472
+ checkMouth(seen);
3473
+ for (const l of layers) {
3474
+ if (l.rowRuns === void 0 && isLipRole(l.role)) {
3475
+ throw new Error(
3476
+ `auto-rig: layer "${l.fileName}": the lip set needs rowRuns (the fold reads the opening's rows off them; measure with createLayerSetMeasurer)`
3477
+ );
3478
+ }
3479
+ }
3411
3480
  checkArmsHaveBody(seen);
3412
3481
  checkPosesHaveArms(seen);
3413
3482
  }
@@ -3527,7 +3596,182 @@ function checkStrandEdges(strand, irises, hair) {
3527
3596
  }
3528
3597
 
3529
3598
  // src/auto-rig/context.ts
3530
- import { StandardParameter as P6 } from "@ikijs/format";
3599
+ import { StandardParameter as P7 } from "@ikijs/format";
3600
+
3601
+ // src/auto-rig/mouth.ts
3602
+ import {
3603
+ StandardParameter as P6
3604
+ } from "@ikijs/format";
3605
+ var UPPER_SHARE = 0.3;
3606
+ var MOUTH_KNOT_PX = 4;
3607
+ var isLipSet = (byRole) => LIP_ROLES.every((r) => byRole.has(r));
3608
+ function columnRuns(layer) {
3609
+ const cols = /* @__PURE__ */ new Map();
3610
+ (layer.rowRuns ?? []).forEach((runs, k) => {
3611
+ for (let i = 0; i < runs.length; i += 2) {
3612
+ for (let c = runs[i]; c < runs[i + 1]; c++) {
3613
+ const list = cols.get(c) ?? [];
3614
+ const last = list[list.length - 1];
3615
+ if (last !== void 0 && last[1] === k) last[1] = k + 1;
3616
+ else list.push([k, k + 1]);
3617
+ cols.set(c, list);
3618
+ }
3619
+ }
3620
+ });
3621
+ return cols;
3622
+ }
3623
+ var edgesOf = (layer, run) => ({
3624
+ top: layer.canvasH / 2 - (layer.bbox.y + run[0]),
3625
+ bottom: layer.canvasH / 2 - (layer.bbox.y + run[1])
3626
+ });
3627
+ function mouthOpening(byRole) {
3628
+ const inner = byRole.get("mouth_inner");
3629
+ const upper = byRole.get("lip_upper");
3630
+ const lower = byRole.get("lip_lower");
3631
+ const innerCols = columnRuns(inner);
3632
+ if (innerCols.size === 0) {
3633
+ throw new LayerGeometryError(
3634
+ `auto-rig: layer "${inner.fileName}": mouth_inner has no opaque pixel (alpha \u2265 128) to read the opening off`
3635
+ );
3636
+ }
3637
+ const upperCols = columnRuns(upper);
3638
+ const lowerCols = columnRuns(lower);
3639
+ const cols = [...innerCols.keys()];
3640
+ const x0 = Math.min(...cols);
3641
+ const x1 = Math.max(...cols);
3642
+ const read = (col) => {
3643
+ const runs = innerCols.get(col);
3644
+ if (runs === void 0) return void 0;
3645
+ const T = edgesOf(inner, runs[0]).top;
3646
+ const Bb = edgesOf(inner, runs[runs.length - 1]).bottom;
3647
+ const line = [...upperCols.get(col) ?? []].reverse().map((r) => edgesOf(upper, r)).find((e) => e.top >= T);
3648
+ const Tu = line?.bottom ?? T;
3649
+ const lineTop = line?.top ?? T;
3650
+ const skin = lowerCols.get(col)?.[0];
3651
+ return {
3652
+ T,
3653
+ Bb,
3654
+ Tu,
3655
+ lineTop,
3656
+ lineH: lineTop - Tu,
3657
+ Bl: skin === void 0 ? Bb : edgesOf(lower, skin).top,
3658
+ H: T - Bb,
3659
+ seam: T - UPPER_SHARE * (T - Bb)
3660
+ };
3661
+ };
3662
+ const reads = [];
3663
+ for (let c = x0; c <= x1; c++) {
3664
+ let r;
3665
+ for (let d = 0; (r = read(c - d) ?? read(c + d)) === void 0; d++) ;
3666
+ reads.push(r);
3667
+ }
3668
+ const base = (col) => reads[Math.min(x1, Math.max(x0, col)) - x0];
3669
+ const centre = Math.round((x0 + x1) / 2);
3670
+ const w = base(centre).lineH;
3671
+ return {
3672
+ x0,
3673
+ x1,
3674
+ centre,
3675
+ canvasW: inner.canvasW,
3676
+ w,
3677
+ at(col) {
3678
+ const b = base(col);
3679
+ return { ...b, overlap: Math.max(0, Math.min(w, b.lineH)) };
3680
+ }
3681
+ };
3682
+ }
3683
+ function mouthFold(role, opening) {
3684
+ return (col, y, v) => {
3685
+ if (v === 1) return 0;
3686
+ if (role !== "mouth_inner" && (col < opening.x0 || col > opening.x1))
3687
+ return 0;
3688
+ const c = opening.at(col);
3689
+ const k = 1 - v;
3690
+ switch (role) {
3691
+ case "lip_upper":
3692
+ return (c.seam - c.Tu) * k;
3693
+ case "lip_lower":
3694
+ return (c.seam + c.overlap - c.Bl) * k;
3695
+ case "mouth_inner":
3696
+ return (c.seam + c.overlap - y) * k;
3697
+ }
3698
+ };
3699
+ }
3700
+ function columnOfKnot(knot, opening, role) {
3701
+ const boundary = Math.round(knot + opening.canvasW / 2);
3702
+ const col = boundary === opening.x1 + 1 ? opening.x1 : boundary;
3703
+ if (col >= opening.x0 && col <= opening.x1) return col;
3704
+ return role === "mouth_inner" ? Math.min(opening.x1, Math.max(opening.x0, col)) : void 0;
3705
+ }
3706
+ function mouthFoldWarp(role, mesh, xs, box, opening) {
3707
+ const fold = mouthFold(role, opening);
3708
+ return localWarp(P6.MouthOpen, mesh, box, [0, 1], (p, v, i) => {
3709
+ const col = columnOfKnot(xs[i % xs.length], opening, role);
3710
+ return [0, col === void 0 ? 0 : fold(col, p[1], v)];
3711
+ });
3712
+ }
3713
+ function mouthKnots(union, opening) {
3714
+ const half = opening.canvasW / 2;
3715
+ const step = MOUTH_KNOT_PX;
3716
+ const xs = [union.x0];
3717
+ while (xs[xs.length - 1] + step < union.x1 - 0.3 * step)
3718
+ xs.push(xs[xs.length - 1] + step);
3719
+ xs.push(union.x1);
3720
+ const fixed = /* @__PURE__ */ new Set([union.x0, union.x1]);
3721
+ for (const x of [opening.x0 - half, opening.x1 + 1 - half]) {
3722
+ fixed.add(x);
3723
+ if (xs.includes(x)) continue;
3724
+ const near = xs.findIndex((v) => Math.abs(v - x) < 0.3 * step);
3725
+ if (near >= 0 && !fixed.has(xs[near])) xs[near] = x;
3726
+ else xs.push(x);
3727
+ }
3728
+ xs.sort((p, q) => p - q);
3729
+ for (const x of xs) {
3730
+ if (!Number.isInteger(x + half)) {
3731
+ throw new Error(
3732
+ `auto-rig: mouth knot ${x} is not an integer canvas boundary`
3733
+ );
3734
+ }
3735
+ }
3736
+ return xs;
3737
+ }
3738
+ function mouthMesh(box, knots) {
3739
+ const xs = [box.x0, ...knots.filter((k) => k > box.x0 && k < box.x1), box.x1];
3740
+ const rows = cellsFor(bh(box), MOUTH_KNOT_PX, 2, 40);
3741
+ const ys = Array.from(
3742
+ { length: rows + 1 },
3743
+ (_, r) => box.y1 - bh(box) * r / rows
3744
+ );
3745
+ return { mesh: columnMesh(box, { xs, ys }), xs };
3746
+ }
3747
+ function buildMouthRig(byRole) {
3748
+ const opening = mouthOpening(byRole);
3749
+ const union = unionBoxes(LIP_ROLES.map((r) => boxOfLayer(byRole.get(r))));
3750
+ return {
3751
+ opening,
3752
+ frame: mouthFrameOf(union),
3753
+ knots: mouthKnots(union, opening),
3754
+ union
3755
+ };
3756
+ }
3757
+ function mouthAnchor(byRole) {
3758
+ const box = (role) => boxOfLayer(byRole.get(role));
3759
+ if (isLipSet(byRole)) {
3760
+ const opening = mouthOpening(byRole);
3761
+ return {
3762
+ at: {
3763
+ x: cx(box("mouth_inner")),
3764
+ y: opening.at(opening.centre).seam
3765
+ },
3766
+ box: unionBoxes(LIP_ROLES.map(box))
3767
+ };
3768
+ }
3769
+ const m = box("mouth");
3770
+ return {
3771
+ at: { x: cx(m), y: cy(m) },
3772
+ box: byRole.has("mouth_open") ? unionBoxes([m, box("mouth_open")]) : m
3773
+ };
3774
+ }
3531
3775
 
3532
3776
  // src/auto-rig/head.ts
3533
3777
  function outerEdge(runs, axisX, side) {
@@ -3607,7 +3851,7 @@ function buildHeadFrame(layers, opts) {
3607
3851
  return ya === void 0 || yb === void 0 ? void 0 : (ya + yb) / 2;
3608
3852
  };
3609
3853
  const eyeY = pair("iris_L", "iris_R") ?? pair("eye_L", "eye_R");
3610
- const mouthY = centreY("mouth");
3854
+ const mouthY = mouthAnchor(byRole).at.y;
3611
3855
  const rowY = (r) => canvasH / 2 - (faceLayer.bbox.y + r + 0.5);
3612
3856
  const raw = filledProfile(faceLayer.rowHalfWidths);
3613
3857
  const profile = raw === null ? null : smoothProfile(raw);
@@ -4580,7 +4824,6 @@ function sideIrises(byRole) {
4580
4824
  return pair === void 0 ? {} : { left: pair.left.box, right: pair.right.box };
4581
4825
  }
4582
4826
  function solveContext(frame, byRole, options, hasNose) {
4583
- const box = (role) => boxOfLayer(byRole.get(role));
4584
4827
  const row = (s, strand2, sideSign) => {
4585
4828
  if (strand2 !== void 0) {
4586
4829
  const lo = sideSign < 0 ? strand2.irisOuter : strand2.irisInner;
@@ -4629,8 +4872,7 @@ function solveContext(frame, byRole, options, hasNose) {
4629
4872
  noseBox = nose.denseCore ? boxOfPixels(nose.denseCore, nose.canvasW, nose.canvasH) : boxOfLayer(nose);
4630
4873
  noseAt = { x: cx(noseBox), y: cy(noseBox) };
4631
4874
  }
4632
- const mouthBox = byRole.has("mouth_open") ? unionBoxes([box("mouth"), box("mouth_open")]) : box("mouth");
4633
- const m = box("mouth");
4875
+ const mouth = mouthAnchor(byRole);
4634
4876
  return {
4635
4877
  frame,
4636
4878
  left,
@@ -4642,8 +4884,8 @@ function solveContext(frame, byRole, options, hasNose) {
4642
4884
  eyeBoxes: { left: eyes.left.box, right: eyes.right.box },
4643
4885
  noseAt,
4644
4886
  noseBox,
4645
- mouthAt: { x: cx(m), y: cy(m) },
4646
- mouthBox
4887
+ mouthAt: mouth.at,
4888
+ mouthBox: mouth.box
4647
4889
  };
4648
4890
  }
4649
4891
  function renderedLander(turn, parts, grids, from = /* @__PURE__ */ new Map()) {
@@ -4659,7 +4901,7 @@ function renderedLander(turn, parts, grids, from = /* @__PURE__ */ new Map()) {
4659
4901
  const family = roleSpec(role).family;
4660
4902
  const part = parts.find((p) => p.id === role);
4661
4903
  const g = grids.get(family);
4662
- const warp = part?.warps?.find((w) => w.parameter === P6.AngleX);
4904
+ const warp = part?.warps?.find((w) => w.parameter === P7.AngleX);
4663
4905
  if (part === void 0 || part.mesh === void 0 || !g && !warp) {
4664
4906
  return x + field(role)(x, y, ax, 0)[0];
4665
4907
  }
@@ -5456,6 +5698,7 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
5456
5698
  hh: frame.hh,
5457
5699
  pivots: [...arms.values()].map((g) => g.shoulder)
5458
5700
  }) : void 0;
5701
+ const lips = isLipSet(byRole) ? buildMouthRig(byRole) : void 0;
5459
5702
  const parts = [];
5460
5703
  const reach = /* @__PURE__ */ new Map();
5461
5704
  let regionOf = [];
@@ -5487,7 +5730,8 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
5487
5730
  frame,
5488
5731
  byRole,
5489
5732
  style,
5490
- landmarks.hairFrontGrid
5733
+ landmarks.hairFrontGrid,
5734
+ lips
5491
5735
  );
5492
5736
  if (built.region !== void 0) {
5493
5737
  regionOf = built.region;
@@ -5569,14 +5813,14 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
5569
5813
  function turnWarps(mesh, b, fieldOf) {
5570
5814
  return [
5571
5815
  localWarp(
5572
- P7.AngleX,
5816
+ P8.AngleX,
5573
5817
  mesh,
5574
5818
  b,
5575
5819
  X_STOPS,
5576
5820
  ([x, y], v, i) => v === 0 ? [0, 0] : [fieldOf(i)(x, y, v, 0)[0], 0]
5577
5821
  ),
5578
5822
  localWarp(
5579
- P7.AngleY,
5823
+ P8.AngleY,
5580
5824
  mesh,
5581
5825
  b,
5582
5826
  Y_STOPS,
@@ -5589,7 +5833,7 @@ function plateFields(turn, region, head) {
5589
5833
  const neck = neckField(turn);
5590
5834
  return (i) => region[i] === "ear" ? ear : region[i] === "neck" ? neck : head;
5591
5835
  }
5592
- function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5836
+ function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
5593
5837
  const box = (role) => boxOfLayer(byRole.get(role));
5594
5838
  const part = {
5595
5839
  id: spec.role,
@@ -5604,6 +5848,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5604
5848
  const bindings = [];
5605
5849
  let extra = [0, 0];
5606
5850
  let mesh;
5851
+ let knotXs;
5607
5852
  let region;
5608
5853
  let headStart = 0;
5609
5854
  if (spec.role === "face") {
@@ -5614,6 +5859,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5614
5859
  headStart = fm.headStart;
5615
5860
  } else if (spec.role === "hair_front") {
5616
5861
  mesh = columnMesh(b, frontGrid);
5862
+ } else if (lips !== void 0 && isLipRole(spec.role)) {
5863
+ ({ mesh, xs: knotXs } = mouthMesh(b, lips.knots));
5617
5864
  } else if (spec.family !== "body") {
5618
5865
  const cells = MESH_CELLS[spec.role] ?? FEATURE_MESH_CELLS;
5619
5866
  const px = Math.min(cells.px, meshScale(spec.role, frame));
@@ -5624,7 +5871,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5624
5871
  }
5625
5872
  if (mesh !== void 0) part.mesh = mesh;
5626
5873
  const side = spec.role.endsWith("_L") ? "L" : "R";
5627
- const eyeOpen = side === "L" ? P7.EyeOpenLeft : P7.EyeOpenRight;
5874
+ const eyeOpen = side === "L" ? P8.EyeOpenLeft : P8.EyeOpenRight;
5628
5875
  const hh = frame.hh;
5629
5876
  switch (spec.role) {
5630
5877
  case "face":
@@ -5639,7 +5886,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5639
5886
  headOwnRoll(byRole.has("body"))
5640
5887
  ),
5641
5888
  localWarp(
5642
- P7.Breath,
5889
+ P8.Breath,
5643
5890
  mesh,
5644
5891
  b,
5645
5892
  [0, 1],
@@ -5686,8 +5933,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5686
5933
  if (byRole.has("mouth_open")) {
5687
5934
  warps.push(mouthWiden(mesh, b));
5688
5935
  bindings.push(
5689
- { parameter: P7.MouthOpen, channel: "opacity", from: 1, to: 0 },
5690
- { parameter: P7.MouthOpen, channel: "opacity", from: 1, to: 0 }
5936
+ { parameter: P8.MouthOpen, channel: "opacity", from: 1, to: 0 },
5937
+ { parameter: P8.MouthOpen, channel: "opacity", from: 1, to: 0 }
5691
5938
  );
5692
5939
  extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
5693
5940
  } else {
@@ -5697,19 +5944,19 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5697
5944
  );
5698
5945
  bindings.push(
5699
5946
  {
5700
- parameter: P7.MouthOpen,
5947
+ parameter: P8.MouthOpen,
5701
5948
  channel: "scaleY",
5702
5949
  from: 0,
5703
5950
  to: roundTo(grow, 0.01)
5704
5951
  },
5705
5952
  {
5706
- parameter: P7.MouthOpen,
5953
+ parameter: P8.MouthOpen,
5707
5954
  channel: "scaleX",
5708
5955
  from: 0,
5709
5956
  to: roundTo(AMPLITUDE.mouthOpenWidth - 1, 0.01)
5710
5957
  },
5711
5958
  {
5712
- parameter: P7.MouthOpen,
5959
+ parameter: P8.MouthOpen,
5713
5960
  channel: "translateY",
5714
5961
  from: 0,
5715
5962
  to: -roundTo(grow * bh(b) / 2, 0.01)
@@ -5721,17 +5968,26 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5721
5968
  case "mouth_open":
5722
5969
  warps.push(mouthForm(mesh, b), mouthOpenGrow(mesh, b));
5723
5970
  bindings.push({
5724
- parameter: P7.MouthOpen,
5971
+ parameter: P8.MouthOpen,
5725
5972
  channel: "opacity",
5726
5973
  from: 0,
5727
5974
  to: 1
5728
5975
  });
5729
5976
  extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
5730
5977
  break;
5978
+ case "mouth_inner":
5979
+ case "lip_lower":
5980
+ case "lip_upper":
5981
+ warps.push(
5982
+ mouthFoldWarp(spec.role, mesh, knotXs, b, lips.opening),
5983
+ mouthForm(mesh, b, lips.frame),
5984
+ mouthWiden(mesh, b, lips.frame)
5985
+ );
5986
+ break;
5731
5987
  case "blush_L":
5732
5988
  case "blush_R":
5733
5989
  bindings.push({
5734
- parameter: P7.Cheek,
5990
+ parameter: P8.Cheek,
5735
5991
  channel: "opacity",
5736
5992
  from: AMPLITUDE.blushRest,
5737
5993
  to: 1
@@ -5742,8 +5998,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
5742
5998
  if (byRole.has("hair_front")) {
5743
5999
  const amp = style.sway * AMPLITUDE.sway * hh;
5744
6000
  warps.push(
5745
- hairSway(P7.HairSwayX, mesh, b, amp),
5746
- hairSway(P7.HairSwayZ, mesh, b, amp)
6001
+ hairSway(P8.HairSwayX, mesh, b, amp),
6002
+ hairSway(P8.HairSwayZ, mesh, b, amp)
5747
6003
  );
5748
6004
  }
5749
6005
  warps.push(tiltHang(mesh, b, pivot, hairHangWeight(frame.chinY, b.y0)));
@@ -5784,29 +6040,6 @@ function hairFrontGrid(b, frame, boxes) {
5784
6040
  );
5785
6041
  return { xs, ys };
5786
6042
  }
5787
- function columnMesh(b, grid) {
5788
- const { xs } = grid;
5789
- const rows = grid.ys.length - 1;
5790
- const vertices = [];
5791
- const uvs = [];
5792
- for (let r = 0; r <= rows; r++) {
5793
- for (const x of xs) {
5794
- const u = (x - b.x0) / bw(b);
5795
- vertices.push(roundTo(u - 0.5, 1e-5), roundTo(0.5 - r / rows, 1e-5));
5796
- uvs.push(roundTo(u, 1e-5), roundTo(r / rows, 1e-5));
5797
- }
5798
- }
5799
- const n = xs.length;
5800
- const indices = [];
5801
- for (let r = 0; r < rows; r++) {
5802
- for (let c = 0; c + 1 < n; c++) {
5803
- const tl = r * n + c;
5804
- const bl = tl + n;
5805
- indices.push(bl, bl + 1, tl, tl, bl + 1, tl + 1);
5806
- }
5807
- }
5808
- return { vertices, uvs, indices };
5809
- }
5810
6043
  function meshScale(role, frame) {
5811
6044
  switch (role) {
5812
6045
  case "hair_front":
@@ -5832,9 +6065,9 @@ function deformers(frame, grids, bodyWarp, arms, byRole) {
5832
6065
  pivot: { x: roundTo(frame.axisX, 0.01), y: roundTo(frame.chinY, 0.01) },
5833
6066
  bindings: [
5834
6067
  // AngleZ is clockwise-positive (Live2D's); a rotation is CCW-positive.
5835
- { parameter: P7.AngleZ, channel: "rotate", from: roll, to: -roll },
6068
+ { parameter: P8.AngleZ, channel: "rotate", from: roll, to: -roll },
5836
6069
  {
5837
- parameter: P7.Breath,
6070
+ parameter: P8.Breath,
5838
6071
  channel: "translateY",
5839
6072
  from: 0,
5840
6073
  to: roundTo(headOwnBreath(hasBody) * frame.hh, 0.01)
@@ -5852,8 +6085,8 @@ function deformers(frame, grids, bodyWarp, arms, byRole) {
5852
6085
  points: g.lattice.points
5853
6086
  },
5854
6087
  warp2d: {
5855
- parameter: P7.AngleX,
5856
- parameterY: P7.AngleY,
6088
+ parameter: P8.AngleX,
6089
+ parameterY: P8.AngleY,
5857
6090
  valuesX: X_STOPS,
5858
6091
  valuesY: Y_STOPS,
5859
6092
  keyforms2d: g.keyforms.map((offsets) => ({ offsets }))
@@ -5866,16 +6099,16 @@ function hairPhysics() {
5866
6099
  return [
5867
6100
  {
5868
6101
  id: "hairSway",
5869
- input: { parameter: P7.AngleX, weight: 1 },
5870
- output: { parameter: P7.HairSwayX, scale: 5 },
6102
+ input: { parameter: P8.AngleX, weight: 1 },
6103
+ output: { parameter: P8.HairSwayX, scale: 5 },
5871
6104
  mass: 1,
5872
6105
  stiffness: 30,
5873
6106
  damping: 3
5874
6107
  },
5875
6108
  {
5876
6109
  id: "hairTilt",
5877
- input: { parameter: P7.AngleZ, weight: 1 },
5878
- output: { parameter: P7.HairSwayZ, scale: 8 },
6110
+ input: { parameter: P8.AngleZ, weight: 1 },
6111
+ output: { parameter: P8.HairSwayZ, scale: 8 },
5879
6112
  mass: 1,
5880
6113
  stiffness: 25,
5881
6114
  damping: 2.5
@@ -5885,13 +6118,13 @@ function hairPhysics() {
5885
6118
  function declareParameters(roles) {
5886
6119
  const out = [];
5887
6120
  const add = (id, name, min, max, def) => out.push({ id, name, min, max, default: def });
5888
- add(P7.AngleX, "Head Angle", -30, 30, 0);
5889
- add(P7.AngleY, "Head Angle Y", -30, 30, 0);
5890
- add(P7.AngleZ, "Head Angle Z", -30, 30, 0);
6121
+ add(P8.AngleX, "Head Angle", -30, 30, 0);
6122
+ add(P8.AngleY, "Head Angle Y", -30, 30, 0);
6123
+ add(P8.AngleZ, "Head Angle Z", -30, 30, 0);
5891
6124
  if (roles.has("body")) {
5892
- add(P7.BodyAngleX, "Body Angle X", -10, 10, 0);
5893
- add(P7.BodyAngleY, "Body Angle Y", -10, 10, 0);
5894
- add(P7.BodyAngleZ, "Body Angle Z", -10, 10, 0);
6125
+ add(P8.BodyAngleX, "Body Angle X", -10, 10, 0);
6126
+ add(P8.BodyAngleY, "Body Angle Y", -10, 10, 0);
6127
+ add(P8.BodyAngleZ, "Body Angle Z", -10, 10, 0);
5895
6128
  }
5896
6129
  const addPose = (side, pose, angle) => {
5897
6130
  add(pose, `Arm Pose ${side}`, 0, 1, 0);
@@ -5900,40 +6133,40 @@ function declareParameters(roles) {
5900
6133
  const [armMin, armMax] = ARM_RANGE;
5901
6134
  const [elbowMin, elbowMax] = ELBOW_RANGE;
5902
6135
  if (roles.has("arm_L")) {
5903
- add(P7.ArmLeft, "Arm L", armMin, armMax, 0);
5904
- add(P7.ElbowLeft, "Elbow L", elbowMin, elbowMax, 0);
6136
+ add(P8.ArmLeft, "Arm L", armMin, armMax, 0);
6137
+ add(P8.ElbowLeft, "Elbow L", elbowMin, elbowMax, 0);
5905
6138
  if (roles.has("forearm_pose_L"))
5906
- addPose("L", P7.ArmPoseLeft, P7.ArmPoseAngleLeft);
6139
+ addPose("L", P8.ArmPoseLeft, P8.ArmPoseAngleLeft);
5907
6140
  }
5908
6141
  if (roles.has("arm_R")) {
5909
- add(P7.ArmRight, "Arm R", armMin, armMax, 0);
5910
- add(P7.ElbowRight, "Elbow R", elbowMin, elbowMax, 0);
6142
+ add(P8.ArmRight, "Arm R", armMin, armMax, 0);
6143
+ add(P8.ElbowRight, "Elbow R", elbowMin, elbowMax, 0);
5911
6144
  if (roles.has("forearm_pose_R"))
5912
- addPose("R", P7.ArmPoseRight, P7.ArmPoseAngleRight);
6145
+ addPose("R", P8.ArmPoseRight, P8.ArmPoseAngleRight);
5913
6146
  }
5914
- add(P7.EyeOpenLeft, "Eye L", 0, 1, 1);
5915
- add(P7.EyeOpenRight, "Eye R", 0, 1, 1);
6147
+ add(P8.EyeOpenLeft, "Eye L", 0, 1, 1);
6148
+ add(P8.EyeOpenRight, "Eye R", 0, 1, 1);
5916
6149
  if ([...roles].some((r) => /^(iris|pupil|highlight)_/.test(r))) {
5917
- add(P7.EyeballX, "Gaze X", -1, 1, 0);
5918
- add(P7.EyeballY, "Gaze Y", -1, 1, 0);
6150
+ add(P8.EyeballX, "Gaze X", -1, 1, 0);
6151
+ add(P8.EyeballY, "Gaze Y", -1, 1, 0);
5919
6152
  }
5920
- add(P7.MouthOpen, "Mouth Open", 0, 1, 0);
5921
- add(P7.MouthForm, "Mouth Form", -1, 1, 0);
6153
+ add(P8.MouthOpen, "Mouth Open", 0, 1, 0);
6154
+ add(P8.MouthForm, "Mouth Form", -1, 1, 0);
5922
6155
  if (roles.has("brow_L")) {
5923
- add(P7.BrowLeftY, "Brow L Y", -1, 1, 0);
5924
- add(P7.BrowLeftAngle, "Brow L Angle", -1, 1, 0);
6156
+ add(P8.BrowLeftY, "Brow L Y", -1, 1, 0);
6157
+ add(P8.BrowLeftAngle, "Brow L Angle", -1, 1, 0);
5925
6158
  }
5926
6159
  if (roles.has("brow_R")) {
5927
- add(P7.BrowRightY, "Brow R Y", -1, 1, 0);
5928
- add(P7.BrowRightAngle, "Brow R Angle", -1, 1, 0);
6160
+ add(P8.BrowRightY, "Brow R Y", -1, 1, 0);
6161
+ add(P8.BrowRightAngle, "Brow R Angle", -1, 1, 0);
5929
6162
  }
5930
6163
  if (roles.has("blush_L") || roles.has("blush_R")) {
5931
- add(P7.Cheek, "Cheek", 0, 1, 0);
6164
+ add(P8.Cheek, "Cheek", 0, 1, 0);
5932
6165
  }
5933
- add(P7.Breath, "Breath", 0, 1, 0);
6166
+ add(P8.Breath, "Breath", 0, 1, 0);
5934
6167
  if (roles.has("hair_front")) {
5935
- add(P7.HairSwayX, "Hair Sway X", -SWAY_RANGE, SWAY_RANGE, 0);
5936
- add(P7.HairSwayZ, "Hair Sway Z", -SWAY_RANGE, SWAY_RANGE, 0);
6168
+ add(P8.HairSwayX, "Hair Sway X", -SWAY_RANGE, SWAY_RANGE, 0);
6169
+ add(P8.HairSwayZ, "Hair Sway Z", -SWAY_RANGE, SWAY_RANGE, 0);
5937
6170
  }
5938
6171
  return out;
5939
6172
  }