@ikijs/editor 0.33.0 → 0.34.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
@@ -1971,7 +1971,7 @@ var PROFILE_VALUES = {
1971
1971
  noseDepth: 2,
1972
1972
  hairFollow: 1,
1973
1973
  fore: 0.2,
1974
- backShare: 0.1,
1974
+ backShare: 0.3,
1975
1975
  earFar: 0,
1976
1976
  earFore: 0.1,
1977
1977
  nodDown: 0.18,
@@ -2023,7 +2023,8 @@ function deriveProfile(v) {
2023
2023
  * takes it only down to its root, `hairSideField`) rides the face at this
2024
2024
  * share of its translation. */
2025
2025
  hairFollow: v.hairFollow,
2026
- /** The head's outline at the eye row holds: where the front hair draws
2026
+ /** The head's outline at the eye row turns only as the back hair does
2027
+ * (`hairBack`, which this share never undercuts): where the front hair draws
2027
2028
  * it, its outer edge follows the face this much on a row where no back
2028
2029
  * hair paints behind that edge. Where back hair does, the edge rides
2029
2030
  * further, as far as that back hair stays behind it through the turn and
@@ -2032,8 +2033,8 @@ function deriveProfile(v) {
2032
2033
  * rows above the eyes) it follows less where the turn would otherwise
2033
2034
  * carry it further outside its back hair than the nod alone does. */
2034
2035
  outlineFollow: 0,
2035
- /** The back hair's slight counter-motion. */
2036
- hairBack: -v.backShare * v.slide,
2036
+ /** The back hair turns with the head, a share of the plate's slide. */
2037
+ hairBack: v.backShare * v.slide,
2037
2038
  /** The ears lag the plate: the far one's outer edge moves this share of
2038
2039
  * its slide; the near one rides the head. */
2039
2040
  earFar: v.earFar,
@@ -3375,6 +3376,19 @@ function spin(x, y, ox, oy, a) {
3375
3376
  ];
3376
3377
  }
3377
3378
  function faceField(m) {
3379
+ const plate = plateField(m);
3380
+ const floor = foreheadFloor(m);
3381
+ let hold;
3382
+ return (x, y, ax, ay) => {
3383
+ const d = plate(x, y, ax, ay);
3384
+ if (ax === 0 || y <= floor) return d;
3385
+ if (hold === void 0) hold = foreheadHold(m, floor) ?? null;
3386
+ if (hold === null) return d;
3387
+ const s = Math.sign(ax);
3388
+ return [d[0] - s * Math.abs(ax) * hold(x, y, s) / 30, d[1]];
3389
+ };
3390
+ }
3391
+ function plateField(m) {
3378
3392
  const f = m.frame;
3379
3393
  return (x, y, ax, ay) => {
3380
3394
  const s = Math.sign(ax);
@@ -3394,6 +3408,77 @@ function faceField(m) {
3394
3408
  return [dx, nodDy(v, ay, f.hh)];
3395
3409
  };
3396
3410
  }
3411
+ function faceCellPx(frame) {
3412
+ return Math.min(FACE_CELL_PX, 2 * frame.wMax / 14);
3413
+ }
3414
+ var FACE_CELL_PX = 28;
3415
+ var FOREHEAD_FADE = 0.04;
3416
+ function foreheadFloor(m) {
3417
+ const f = m.frame;
3418
+ const eyes = [m.boxes.eye_L, m.boxes.eye_R].filter(
3419
+ (e) => e !== void 0
3420
+ );
3421
+ const eyeTop = eyes.length > 0 ? Math.max(...eyes.map((e) => e.y1)) : f.eyeY;
3422
+ return Math.max(eyeTop, f.ears === void 0 ? -Infinity : f.ears.top + 2);
3423
+ }
3424
+ function foreheadHold(m, floor) {
3425
+ const f = m.frame;
3426
+ const top = f.face.y1;
3427
+ const count = Math.floor(top - floor);
3428
+ if (count <= 0 || f.faceRuns(top - 0.5) === void 0 || f.hairRuns("hair_front", top - 0.5) === void 0) {
3429
+ return void 0;
3430
+ }
3431
+ const plate = plateField(m);
3432
+ const front = hairFrontField(m);
3433
+ const { inner } = hairFrontBends(f, m.boxes);
3434
+ const cell = faceCellPx(f);
3435
+ const fade = FOREHEAD_FADE * f.hh;
3436
+ const sides = [-1, 1];
3437
+ const hold = sides.map(() => new Float64Array(count));
3438
+ const whole = sides.map(() => new Float64Array(count));
3439
+ const span3 = sides.map(() => new Float64Array(count));
3440
+ let any = false;
3441
+ for (let i = 0; i < count; i++) {
3442
+ const y = top - i - 0.5;
3443
+ const faceRuns = f.faceRuns(y);
3444
+ const hairRuns = f.hairRuns("hair_front", y);
3445
+ sides.forEach((side, k) => {
3446
+ const edge = outerEdge(faceRuns, f.axisX, side);
3447
+ const hair = outerEdge(hairRuns, f.axisX, side);
3448
+ if (edge === void 0 || hair === void 0) return;
3449
+ const end = side * (edge - f.axisX) - cell;
3450
+ if (end <= 0) return;
3451
+ const cover = side * (hair - edge);
3452
+ const room = Math.max(0, cover);
3453
+ const goes = side * plate(edge - side * 0.5, y, 30 * side, 0)[0];
3454
+ const held = side * front(hair - side * 0.5, y, 30 * side, 0)[0];
3455
+ const need = clamp(1 + cover / fade, 0, 1) * Math.max(0, goes - held - room + Math.min(1, room));
3456
+ if (need <= 0) return;
3457
+ const over = Math.min(end, Math.max(2 * cell, end - inner, 2 * need));
3458
+ hold[k][i] = Math.min(need, over / 2);
3459
+ whole[k][i] = end;
3460
+ span3[k][i] = over;
3461
+ any = true;
3462
+ });
3463
+ }
3464
+ if (!any) return void 0;
3465
+ const reach = Math.ceil(cell);
3466
+ return (x, y, s) => {
3467
+ const u = s * (x - f.axisX);
3468
+ if (s === 0 || u <= 0 || y <= floor) return 0;
3469
+ const k = s < 0 ? 0 : 1;
3470
+ const i = clamp(Math.floor(top - y), 0, count - 1);
3471
+ let h = 0;
3472
+ const j1 = Math.min(count - 1, i + 3 * reach);
3473
+ for (let j = Math.max(0, i - 3 * reach); j <= j1; j++) {
3474
+ if (hold[k][j] === 0) continue;
3475
+ const near = clamp((3 * reach - Math.abs(i - j)) / (2 * reach), 0, 1);
3476
+ const across = clamp((u - (whole[k][j] - span3[k][j])) / span3[k][j], 0, 1);
3477
+ h = Math.max(h, hold[k][j] * near * across);
3478
+ }
3479
+ return h * clamp((y - floor) / (2 * cell), 0, 1);
3480
+ };
3481
+ }
3397
3482
  function earField(m) {
3398
3483
  const f = m.frame;
3399
3484
  const ears = f.ears;
@@ -3596,7 +3681,7 @@ function hairFrontField(m) {
3596
3681
  )
3597
3682
  );
3598
3683
  const followOf = (x, y, s) => followAt(x, y, s) + (ride === void 0 ? 0 : ride.lift(x, y, s));
3599
- const drift = Math.abs(m.hairBack);
3684
+ const drift = -m.hairBack;
3600
3685
  const boundOf = (x, y, s) => {
3601
3686
  if (ride === void 0) return followOf(x, y, s);
3602
3687
  const b = ride.blend(x, y, s);
@@ -3763,7 +3848,7 @@ function outerRide(m, inner, coreAt, followAt, capLimit, eyeTop, irisTop) {
3763
3848
  if (f.hairRuns("hair_front", top) === void 0 || f.hairRuns("hair_back", top) === void 0) {
3764
3849
  return void 0;
3765
3850
  }
3766
- const drift = Math.abs(m.hairBack);
3851
+ const drift = -m.hairBack;
3767
3852
  const sides = [-1, 1];
3768
3853
  const n = ys.length;
3769
3854
  const count = Math.round(top - ys[n - 1]);
@@ -3894,6 +3979,7 @@ function outerRide(m, inner, coreAt, followAt, capLimit, eyeTop, irisTop) {
3894
3979
  const cuts = new Float64Array(n * 2 * nc);
3895
3980
  const blend = new Float64Array(n * 2 * nc);
3896
3981
  const motion = new Array(n * 2);
3982
+ const held = new Array(n * 2);
3897
3983
  const outward = sides.map((side) => {
3898
3984
  const cols = xs.map((_, c) => c).filter((c) => side * (xs[c] - f.axisX) >= 0);
3899
3985
  return side < 0 ? cols.reverse() : cols;
@@ -3950,22 +4036,24 @@ function outerRide(m, inner, coreAt, followAt, capLimit, eyeTop, irisTop) {
3950
4036
  }
3951
4037
  }
3952
4038
  motion[r * 2 + j] = after.map((v) => v + drift);
4039
+ held[r * 2 + j] = today.map((v) => v + drift);
3953
4040
  for (let c = 0; c < nc; c++)
3954
4041
  cuts[(r * 2 + j) * nc + c] = after[c] - today[c];
3955
4042
  });
3956
4043
  }
4044
+ const at = (r, j, c) => (r * 2 + j) * nc + c;
4045
+ let zero = last + 1;
4046
+ while (zero < n && ys[zero] > eyeTop) zero++;
4047
+ while (zero > last + 1 && zero < n && ys[zero] < irisTop - 0.5) zero--;
4048
+ const endY = zero < n ? ys[zero] : Math.min(Math.max(eyeTop, irisTop - 0.5), ys[n - 1] - 1);
3957
4049
  for (let r = 0; r < n; r++) {
3958
4050
  sides.forEach((s, j) => {
3959
4051
  motion[r * 2 + j] ??= xs.map(
3960
4052
  (x) => followAt(x, ys[r], s) + liftAt(x, ys[r], s) + drift
3961
4053
  );
4054
+ held[r * 2 + j] ??= motion[r * 2 + j];
3962
4055
  });
3963
4056
  }
3964
- const at = (r, j, c) => (r * 2 + j) * nc + c;
3965
- let zero = last + 1;
3966
- while (zero < n && ys[zero] > eyeTop) zero++;
3967
- while (zero > last + 1 && zero < n && ys[zero] < irisTop - 0.5) zero--;
3968
- const endY = zero < n ? ys[zero] : Math.min(Math.max(eyeTop, irisTop - 0.5), ys[n - 1] - 1);
3969
4057
  for (let pass = 0; pass < 8; pass++) {
3970
4058
  const was = blend.slice();
3971
4059
  for (let j = 0; j < 2; j++) {
@@ -3983,11 +4071,15 @@ function outerRide(m, inner, coreAt, followAt, capLimit, eyeTop, irisTop) {
3983
4071
  for (let q = 0; q < r && q <= last; q++) {
3984
4072
  if (wr[q] < 1) continue;
3985
4073
  const k = (ys[r] - endY) / (ys[q] - endY);
3986
- for (let c = 0; c < nc; c++)
3987
- blend[at(r, j, c)] = Math.max(
3988
- blend[at(r, j, c)],
3989
- blend[at(q, j, c)] * k
3990
- );
4074
+ const [mq, hq] = [motion[q * 2 + j], held[q * 2 + j]];
4075
+ const [mr, hr] = [motion[r * 2 + j], held[r * 2 + j]];
4076
+ for (let c = 0; c < nc; c++) {
4077
+ const bq = blend[at(q, j, c)];
4078
+ const ease = hq[c] > 0 ? clamp(1 - (1 - bq) * mq[c] / hq[c], 0, 1) : bq;
4079
+ const want = ease * k;
4080
+ const br = mr[c] > 0 && hr[c] > 0 ? 1 - (1 - want) * hr[c] / mr[c] : want;
4081
+ blend[at(r, j, c)] = Math.max(blend[at(r, j, c)], br);
4082
+ }
3991
4083
  }
3992
4084
  }
3993
4085
  }
@@ -4004,7 +4096,7 @@ function outerRide(m, inner, coreAt, followAt, capLimit, eyeTop, irisTop) {
4004
4096
  }
4005
4097
  if (blend.every((v, i) => v === was[i])) break;
4006
4098
  }
4007
- const cutRow = (r, j, x) => r > last ? 0 : along(cuts.subarray((r * 2 + j) * nc, (r * 2 + j + 1) * nc), x);
4099
+ const cutRow = (r, j, x) => r > last ? 0 : along(cuts.subarray(at(r, j, 0), at(r, j, nc)), x);
4008
4100
  return {
4009
4101
  lift: liftAt,
4010
4102
  // Between vertex rows, as the render interpolates; a vertex reads its own
@@ -4488,6 +4580,7 @@ function buildTurn(frame, landmarks, q, turn) {
4488
4580
  const eyeHalf = eyes.reduce((sum, e) => sum + (e.x1 - e.x0) / 2, 0) / Math.max(1, eyes.length);
4489
4581
  const farScale = 1 + q.fore * A * (turn.eyeFarScale - 1);
4490
4582
  const farCorner = lead(turn.eyeFar) - (1 - farScale) * eyeHalf;
4583
+ const shell = A * hh * turn.hairBack;
4491
4584
  return {
4492
4585
  frame,
4493
4586
  face,
@@ -4505,10 +4598,13 @@ function buildTurn(frame, landmarks, q, turn) {
4505
4598
  mouth: q.mouth ?? lead(turn.mouth),
4506
4599
  mouthWidth: 1 + A * (turn.mouthWidth - 1),
4507
4600
  tilt: A,
4508
- hairFront: q.follow * face,
4509
- hairFar: Math.max(q.follow * face, farCorner),
4510
- hairOuter: q.outline * face,
4511
- hairBack: A * hh * turn.hairBack,
4601
+ // The front hair lies on the head in front of its shell: neither over
4602
+ // the face nor at the outline it draws does it turn less than the back
4603
+ // hair behind it.
4604
+ hairFront: Math.max(q.follow * face, shell),
4605
+ hairFar: Math.max(q.follow * face, shell, farCorner),
4606
+ hairOuter: Math.max(q.outline * face, shell),
4607
+ hairBack: shell,
4512
4608
  shellScale: q.shell,
4513
4609
  earFar: q.earFar,
4514
4610
  earFarScale: 1 + A * (turn.earFarScale - 1),
@@ -4630,7 +4726,7 @@ var RIG_PLAN_FIELDS = [
4630
4726
  fitBy: null,
4631
4727
  provisional: false,
4632
4728
  migratedFrom: "hairFollow",
4633
- what: "the front hair's share of the face's turn"
4729
+ what: "the front hair's share of the face's turn (never less than the back hair's own turn, 0.3 of the face's: a share under that has no effect)"
4634
4730
  },
4635
4731
  {
4636
4732
  path: "hair.outlineFollow",
@@ -4647,7 +4743,7 @@ var RIG_PLAN_FIELDS = [
4647
4743
  fitBy: null,
4648
4744
  provisional: false,
4649
4745
  migratedFrom: "outlineFollow",
4650
- what: "the front hair's outer edge's share where it draws the head's outline (0: the outline holds, though where back hair paints behind it, it may ride further, as far as that back hair reaches; at hair.follow the front hair's side strands ride whole)"
4746
+ what: "the front hair's outer edge's share where it draws the head's outline (never less than the back hair's own turn: at 0 the outline turns only as the back hair does, though where back hair paints behind it, it may ride further, as far as that back hair reaches; at hair.follow the front hair's side strands ride whole)"
4651
4747
  },
4652
4748
  {
4653
4749
  path: "hair.sway",
@@ -7338,7 +7434,7 @@ function writeOcclusion(customParts, built, parts) {
7338
7434
 
7339
7435
  // src/auto-rig/generate.ts
7340
7436
  var MESH_CELLS = {
7341
- face: { px: 28, min: 4, max: 24 },
7437
+ face: { px: FACE_CELL_PX, min: 4, max: 24 },
7342
7438
  // The front hair eases from riding the face to holding the outline.
7343
7439
  hair_front: { px: 32, min: 3, max: 24 },
7344
7440
  // A side lock's rows come from `sideLockGrid`: even ones, plus the root
@@ -7646,7 +7742,7 @@ function buildPart(spec, b, order, frame, byRole, plan, frontGrid, lips) {
7646
7742
  let region;
7647
7743
  let headStart = 0;
7648
7744
  if (spec.role === "face") {
7649
- const px = Math.min(MESH_CELLS.face.px, 2 * frame.wMax / 14);
7745
+ const px = faceCellPx(frame);
7650
7746
  const fm = buildFaceMesh(frame, b, { ...MESH_CELLS.face, px }, NECK_CELLS);
7651
7747
  mesh = fm.mesh;
7652
7748
  region = fm.region;