@ikijs/editor 0.32.1 → 0.33.1

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
@@ -2937,6 +2937,7 @@ function mouthAnchor(byRole) {
2937
2937
  }
2938
2938
 
2939
2939
  // src/auto-rig/head.ts
2940
+ var EAR_EDGE = 3;
2940
2941
  function outerEdge(runs, axisX, side) {
2941
2942
  if (runs.length === 0) return void 0;
2942
2943
  const x = side > 0 ? runs[runs.length - 1] : runs[0];
@@ -2970,7 +2971,11 @@ function findEars(raw, wMax, firstRow, lastRow, minRows, maxRows) {
2970
2971
  if (raw[t] - raw[t - EAR_ROWS] < step) continue;
2971
2972
  for (let b = t + minRows; b <= Math.min(lastRow, t + maxRows); b++) {
2972
2973
  if (raw[b - EAR_ROWS] - raw[b] >= step) {
2973
- return { top: t - EAR_ROWS, bottom: b };
2974
+ let end = b;
2975
+ for (let k = b + 1; k <= Math.min(lastRow, b + minRows); k++) {
2976
+ if (raw[k - EAR_ROWS] - raw[k] >= step) end = k;
2977
+ }
2978
+ return { top: t - EAR_ROWS, bottom: end };
2974
2979
  }
2975
2980
  }
2976
2981
  return void 0;
@@ -3048,6 +3053,7 @@ function buildHeadFrame(layers, opts) {
3048
3053
  return rows[i] + (rows[j] - rows[i]) * clamp(r - i, 0, 1);
3049
3054
  };
3050
3055
  const rawRows = raw;
3056
+ let earRows;
3051
3057
  const earless = (chin) => {
3052
3058
  const rowOf = (y) => Math.round(canvasH / 2 - y - faceLayer.bbox.y - 0.5);
3053
3059
  const span3 = Math.max(1, eyeY - chin);
@@ -3070,8 +3076,10 @@ function buildHeadFrame(layers, opts) {
3070
3076
  top: rowY(top) + 0.5,
3071
3077
  bottom: rowY(bottom) - 0.5,
3072
3078
  attachAt: (y) => attach(clamp(canvasH / 2 - y - faceLayer.bbox.y - 0.5, top, bottom)),
3073
- outer
3079
+ outer,
3080
+ edgeAt: (y) => attach(clamp(canvasH / 2 - y - faceLayer.bbox.y - 0.5, top, bottom)) + EAR_EDGE
3074
3081
  };
3082
+ earRows = { top, bottom };
3075
3083
  return smoothProfile(
3076
3084
  rawRows.map((v, r) => r > top && r < bottom ? attach(r) : v)
3077
3085
  );
@@ -3193,9 +3201,13 @@ function buildHeadFrame(layers, opts) {
3193
3201
  headHalfAt = readRows(head);
3194
3202
  headMax = Math.max(...head);
3195
3203
  if (ears !== void 0) {
3204
+ const band = earRows;
3205
+ const edge = head.map(
3206
+ (w, r) => r >= band.top && r <= band.bottom ? w + EAR_EDGE : w
3207
+ );
3196
3208
  const bottomOf = (d) => {
3197
- for (let r = head.length - 1; r >= 0; r--) {
3198
- if (head[r] >= d) return rowY(r) - 0.5;
3209
+ for (let r = edge.length - 1; r >= 0; r--) {
3210
+ if (edge[r] >= d) return rowY(r) - 0.5;
3199
3211
  }
3200
3212
  return face.y1;
3201
3213
  };
@@ -3363,6 +3375,19 @@ function spin(x, y, ox, oy, a) {
3363
3375
  ];
3364
3376
  }
3365
3377
  function faceField(m) {
3378
+ const plate = plateField(m);
3379
+ const floor = foreheadFloor(m);
3380
+ let hold;
3381
+ return (x, y, ax, ay) => {
3382
+ const d = plate(x, y, ax, ay);
3383
+ if (ax === 0 || y <= floor) return d;
3384
+ if (hold === void 0) hold = foreheadHold(m, floor) ?? null;
3385
+ if (hold === null) return d;
3386
+ const s = Math.sign(ax);
3387
+ return [d[0] - s * Math.abs(ax) * hold(x, y, s) / 30, d[1]];
3388
+ };
3389
+ }
3390
+ function plateField(m) {
3366
3391
  const f = m.frame;
3367
3392
  return (x, y, ax, ay) => {
3368
3393
  const s = Math.sign(ax);
@@ -3382,6 +3407,77 @@ function faceField(m) {
3382
3407
  return [dx, nodDy(v, ay, f.hh)];
3383
3408
  };
3384
3409
  }
3410
+ function faceCellPx(frame) {
3411
+ return Math.min(FACE_CELL_PX, 2 * frame.wMax / 14);
3412
+ }
3413
+ var FACE_CELL_PX = 28;
3414
+ var FOREHEAD_FADE = 0.04;
3415
+ function foreheadFloor(m) {
3416
+ const f = m.frame;
3417
+ const eyes = [m.boxes.eye_L, m.boxes.eye_R].filter(
3418
+ (e) => e !== void 0
3419
+ );
3420
+ const eyeTop = eyes.length > 0 ? Math.max(...eyes.map((e) => e.y1)) : f.eyeY;
3421
+ return Math.max(eyeTop, f.ears === void 0 ? -Infinity : f.ears.top + 2);
3422
+ }
3423
+ function foreheadHold(m, floor) {
3424
+ const f = m.frame;
3425
+ const top = f.face.y1;
3426
+ const count = Math.floor(top - floor);
3427
+ if (count <= 0 || f.faceRuns(top - 0.5) === void 0 || f.hairRuns("hair_front", top - 0.5) === void 0) {
3428
+ return void 0;
3429
+ }
3430
+ const plate = plateField(m);
3431
+ const front = hairFrontField(m);
3432
+ const { inner } = hairFrontBends(f, m.boxes);
3433
+ const cell = faceCellPx(f);
3434
+ const fade = FOREHEAD_FADE * f.hh;
3435
+ const sides = [-1, 1];
3436
+ const hold = sides.map(() => new Float64Array(count));
3437
+ const whole = sides.map(() => new Float64Array(count));
3438
+ const span3 = sides.map(() => new Float64Array(count));
3439
+ let any = false;
3440
+ for (let i = 0; i < count; i++) {
3441
+ const y = top - i - 0.5;
3442
+ const faceRuns = f.faceRuns(y);
3443
+ const hairRuns = f.hairRuns("hair_front", y);
3444
+ sides.forEach((side, k) => {
3445
+ const edge = outerEdge(faceRuns, f.axisX, side);
3446
+ const hair = outerEdge(hairRuns, f.axisX, side);
3447
+ if (edge === void 0 || hair === void 0) return;
3448
+ const end = side * (edge - f.axisX) - cell;
3449
+ if (end <= 0) return;
3450
+ const cover = side * (hair - edge);
3451
+ const room = Math.max(0, cover);
3452
+ const goes = side * plate(edge - side * 0.5, y, 30 * side, 0)[0];
3453
+ const held = side * front(hair - side * 0.5, y, 30 * side, 0)[0];
3454
+ const need = clamp(1 + cover / fade, 0, 1) * Math.max(0, goes - held - room + Math.min(1, room));
3455
+ if (need <= 0) return;
3456
+ const over = Math.min(end, Math.max(2 * cell, end - inner, 2 * need));
3457
+ hold[k][i] = Math.min(need, over / 2);
3458
+ whole[k][i] = end;
3459
+ span3[k][i] = over;
3460
+ any = true;
3461
+ });
3462
+ }
3463
+ if (!any) return void 0;
3464
+ const reach = Math.ceil(cell);
3465
+ return (x, y, s) => {
3466
+ const u = s * (x - f.axisX);
3467
+ if (s === 0 || u <= 0 || y <= floor) return 0;
3468
+ const k = s < 0 ? 0 : 1;
3469
+ const i = clamp(Math.floor(top - y), 0, count - 1);
3470
+ let h = 0;
3471
+ const j1 = Math.min(count - 1, i + 3 * reach);
3472
+ for (let j = Math.max(0, i - 3 * reach); j <= j1; j++) {
3473
+ if (hold[k][j] === 0) continue;
3474
+ const near = clamp((3 * reach - Math.abs(i - j)) / (2 * reach), 0, 1);
3475
+ const across = clamp((u - (whole[k][j] - span3[k][j])) / span3[k][j], 0, 1);
3476
+ h = Math.max(h, hold[k][j] * near * across);
3477
+ }
3478
+ return h * clamp((y - floor) / (2 * cell), 0, 1);
3479
+ };
3480
+ }
3385
3481
  function earField(m) {
3386
3482
  const f = m.frame;
3387
3483
  const ears = f.ears;
@@ -3395,7 +3491,7 @@ function earField(m) {
3395
3491
  const attach = ears.attachAt(y);
3396
3492
  const span3 = Math.max(1, ears.outer - attach);
3397
3493
  if (sideOf(m, x, s) === "far") {
3398
- const rim2 = TURN.earFar * m.face;
3494
+ const rim2 = m.earFar * m.face;
3399
3495
  const root2 = earRoot(m, face, y, s);
3400
3496
  const lead = Math.max(
3401
3497
  0,
@@ -3420,7 +3516,7 @@ function farEarCovered(m) {
3420
3516
  const ears = m.frame.ears;
3421
3517
  if (ears === void 0) return true;
3422
3518
  const face = faceField(m);
3423
- const rim = TURN.earFar * m.face;
3519
+ const rim = m.earFar * m.face;
3424
3520
  const y0 = ears.bottom - 2;
3425
3521
  const y1 = ears.top + 2;
3426
3522
  const n = Math.max(1, Math.ceil(y1 - y0));
@@ -4261,6 +4357,7 @@ function fitTurn(ctx, targets, plan, build) {
4261
4357
  lead: plan.turn.featureLead,
4262
4358
  follow: plan.hair.follow,
4263
4359
  outline: plan.hair.outlineFollow,
4360
+ earFar: plan.turn.farEar,
4264
4361
  fore: targets.farEyeRatio === void 0 ? plan.turn.eyeForeshorten : 1,
4265
4362
  shell: 1,
4266
4363
  noseLead: targets.noseShift !== void 0 || plan.turn.noseLead === 1 ? void 0 : plan.turn.noseLead
@@ -4497,6 +4594,7 @@ function buildTurn(frame, landmarks, q, turn) {
4497
4594
  hairOuter: q.outline * face,
4498
4595
  hairBack: A * hh * turn.hairBack,
4499
4596
  shellScale: q.shell,
4597
+ earFar: q.earFar,
4500
4598
  earFarScale: 1 + A * (turn.earFarScale - 1),
4501
4599
  ...landmarks
4502
4600
  };
@@ -4584,6 +4682,23 @@ var RIG_PLAN_FIELDS = [
4584
4682
  migratedFrom: null,
4585
4683
  what: "how far the nose leads the face plate on the turn \u2014 turn only, the nod keeps the profile's nose depth"
4586
4684
  },
4685
+ {
4686
+ path: "turn.farEar",
4687
+ group: "turn",
4688
+ key: "farEar",
4689
+ kind: "absolute",
4690
+ unit: "share of the face plate's slide",
4691
+ default: PROFILE.TURN.earFar,
4692
+ range: [0, 1],
4693
+ recommended: [0, 0.5],
4694
+ step: 0.1,
4695
+ poses: TURN_POSES,
4696
+ cue: null,
4697
+ fitBy: null,
4698
+ provisional: false,
4699
+ migratedFrom: null,
4700
+ what: "how far the far ear's outer edge follows the face on the turn (0: it holds and the head slides over it, about half of a default-sized ear going under the face; 1: it rides with the face and stays whole)"
4701
+ },
4587
4702
  {
4588
4703
  path: "hair.follow",
4589
4704
  group: "hair",
@@ -6682,6 +6797,11 @@ function buildFaceMesh(frame, b, head, neckCells) {
6682
6797
  frame.axisX - frame.headMax + 0.5,
6683
6798
  frame.axisX + frame.headMax - 0.5
6684
6799
  ];
6800
+ const ears2 = frame.ears;
6801
+ if (ears2 !== void 0 && neck2 === void 0) {
6802
+ const d = ears2.edgeAt(ears2.bottom);
6803
+ at.push(frame.axisX - d, frame.axisX + d);
6804
+ }
6685
6805
  if (neck2 !== void 0) {
6686
6806
  at.push(
6687
6807
  frame.axisX,
@@ -7302,7 +7422,7 @@ function writeOcclusion(customParts, built, parts) {
7302
7422
 
7303
7423
  // src/auto-rig/generate.ts
7304
7424
  var MESH_CELLS = {
7305
- face: { px: 28, min: 4, max: 24 },
7425
+ face: { px: FACE_CELL_PX, min: 4, max: 24 },
7306
7426
  // The front hair eases from riding the face to holding the outline.
7307
7427
  hair_front: { px: 32, min: 3, max: 24 },
7308
7428
  // A side lock's rows come from `sideLockGrid`: even ones, plus the root
@@ -7610,7 +7730,7 @@ function buildPart(spec, b, order, frame, byRole, plan, frontGrid, lips) {
7610
7730
  let region;
7611
7731
  let headStart = 0;
7612
7732
  if (spec.role === "face") {
7613
- const px = Math.min(MESH_CELLS.face.px, 2 * frame.wMax / 14);
7733
+ const px = faceCellPx(frame);
7614
7734
  const fm = buildFaceMesh(frame, b, { ...MESH_CELLS.face, px }, NECK_CELLS);
7615
7735
  mesh = fm.mesh;
7616
7736
  region = fm.region;