@ikijs/editor 0.32.0 → 0.33.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/README.md CHANGED
@@ -78,7 +78,7 @@ edge further outside the back hair painted behind it than the nod alone does —
78
78
  and the back hair stays behind; the neck and the
79
79
  ears the face layer paints are cut off into islands of the plate's own mesh,
80
80
  drawn behind the head — the neck stays (only the chin's shade slides across
81
- it), the ears lag the face, the far one narrowing. AngleZ rolls the head 14° about the chin.
81
+ it), the ears lag the face, the far one held while the head slides partly over it (about half of an ear of the default size). AngleZ rolls the head 14° about the chin.
82
82
  A `body` rides a body warp that breathes, follows the head's turn and tilt a
83
83
  little and turns on `ParamBodyAngleX/Y/Z`, its legs planted, and the head
84
84
  hangs from it. `arm_L` / `arm_R`, each a whole arm drawn hanging with its
@@ -97,7 +97,7 @@ the turn's amount, the eyes' foreshortening, the hair's width, a feature's own
97
97
  shift. With none given the profile itself renders (`DEFAULT_TURN_TARGETS`
98
98
  expresses it in the cues' units). `options.rigPlan` tunes a character from the
99
99
  profile in three groups (`turn`, `hair`, `eyes`: the whole turn, the
100
- features' lead, the front hair's follow and its outline's, the blink and the
100
+ features' lead, the far ear's follow, the front hair's follow and its outline's, the blink and the
101
101
  hair sway), validated against `RIG_PLAN_FIELDS`. `options.patches` adds displacements to a generated rig's keyform
102
102
  stops (`RigPatch`), replayed on every rig and reported through
103
103
  `options.onPatchesApplied`; a refused op throws `PatchError`. `options.customParts` adds parts the
package/dist/index.d.mts CHANGED
@@ -709,6 +709,9 @@ interface RigPlan {
709
709
  /** How far the nose leads the plate (turn only — the nod keeps the
710
710
  * profile's nose depth). */
711
711
  noseLead?: number;
712
+ /** How far the far ear's outer edge follows the face (0: it holds and
713
+ * the head slides over it; 1: it rides with the face and stays whole). */
714
+ farEar?: number;
712
715
  };
713
716
  hair?: {
714
717
  /** The front hair's share of the face's turn. */
package/dist/index.d.ts CHANGED
@@ -709,6 +709,9 @@ interface RigPlan {
709
709
  /** How far the nose leads the plate (turn only — the nod keeps the
710
710
  * profile's nose depth). */
711
711
  noseLead?: number;
712
+ /** How far the far ear's outer edge follows the face (0: it holds and
713
+ * the head slides over it; 1: it rides with the face and stays whole). */
714
+ farEar?: number;
712
715
  };
713
716
  hair?: {
714
717
  /** The front hair's share of the face's turn. */
package/dist/index.js CHANGED
@@ -2064,7 +2064,8 @@ var PROFILE_VALUES = {
2064
2064
  hairFollow: 1,
2065
2065
  fore: 0.2,
2066
2066
  backShare: 0.1,
2067
- earFar: 0.5,
2067
+ earFar: 0,
2068
+ earFore: 0.1,
2068
2069
  nodDown: 0.18,
2069
2070
  nodUp: 0.1,
2070
2071
  nodLead: 0.06,
@@ -2131,7 +2132,7 @@ function deriveProfile(v) {
2131
2132
  earNear: 1,
2132
2133
  /** The far ear's width at full turn, about its outer edge, where the head
2133
2134
  * leaves it room; less where it does not. */
2134
- earFarScale: 1 - v.fore
2135
+ earFarScale: 1 - v.earFore
2135
2136
  };
2136
2137
  const mid = v.nodDown * (1 + v.chinShare) / 2;
2137
2138
  const feature = (depth) => ({
@@ -3024,6 +3025,7 @@ function mouthAnchor(byRole) {
3024
3025
  }
3025
3026
 
3026
3027
  // src/auto-rig/head.ts
3028
+ var EAR_EDGE = 3;
3027
3029
  function outerEdge(runs, axisX, side) {
3028
3030
  if (runs.length === 0) return void 0;
3029
3031
  const x = side > 0 ? runs[runs.length - 1] : runs[0];
@@ -3057,7 +3059,11 @@ function findEars(raw, wMax, firstRow, lastRow, minRows, maxRows) {
3057
3059
  if (raw[t] - raw[t - EAR_ROWS] < step) continue;
3058
3060
  for (let b = t + minRows; b <= Math.min(lastRow, t + maxRows); b++) {
3059
3061
  if (raw[b - EAR_ROWS] - raw[b] >= step) {
3060
- return { top: t - EAR_ROWS, bottom: b };
3062
+ let end = b;
3063
+ for (let k = b + 1; k <= Math.min(lastRow, b + minRows); k++) {
3064
+ if (raw[k - EAR_ROWS] - raw[k] >= step) end = k;
3065
+ }
3066
+ return { top: t - EAR_ROWS, bottom: end };
3061
3067
  }
3062
3068
  }
3063
3069
  return void 0;
@@ -3135,6 +3141,7 @@ function buildHeadFrame(layers, opts) {
3135
3141
  return rows[i] + (rows[j] - rows[i]) * clamp(r - i, 0, 1);
3136
3142
  };
3137
3143
  const rawRows = raw;
3144
+ let earRows;
3138
3145
  const earless = (chin) => {
3139
3146
  const rowOf = (y) => Math.round(canvasH / 2 - y - faceLayer.bbox.y - 0.5);
3140
3147
  const span3 = Math.max(1, eyeY - chin);
@@ -3157,8 +3164,10 @@ function buildHeadFrame(layers, opts) {
3157
3164
  top: rowY(top) + 0.5,
3158
3165
  bottom: rowY(bottom) - 0.5,
3159
3166
  attachAt: (y) => attach(clamp(canvasH / 2 - y - faceLayer.bbox.y - 0.5, top, bottom)),
3160
- outer
3167
+ outer,
3168
+ edgeAt: (y) => attach(clamp(canvasH / 2 - y - faceLayer.bbox.y - 0.5, top, bottom)) + EAR_EDGE
3161
3169
  };
3170
+ earRows = { top, bottom };
3162
3171
  return smoothProfile(
3163
3172
  rawRows.map((v, r) => r > top && r < bottom ? attach(r) : v)
3164
3173
  );
@@ -3280,9 +3289,13 @@ function buildHeadFrame(layers, opts) {
3280
3289
  headHalfAt = readRows(head);
3281
3290
  headMax = Math.max(...head);
3282
3291
  if (ears !== void 0) {
3292
+ const band = earRows;
3293
+ const edge = head.map(
3294
+ (w, r) => r >= band.top && r <= band.bottom ? w + EAR_EDGE : w
3295
+ );
3283
3296
  const bottomOf = (d) => {
3284
- for (let r = head.length - 1; r >= 0; r--) {
3285
- if (head[r] >= d) return rowY(r) - 0.5;
3297
+ for (let r = edge.length - 1; r >= 0; r--) {
3298
+ if (edge[r] >= d) return rowY(r) - 0.5;
3286
3299
  }
3287
3300
  return face.y1;
3288
3301
  };
@@ -3482,7 +3495,7 @@ function earField(m) {
3482
3495
  const attach = ears.attachAt(y);
3483
3496
  const span3 = Math.max(1, ears.outer - attach);
3484
3497
  if (sideOf(m, x, s) === "far") {
3485
- const rim2 = TURN.earFar * m.face;
3498
+ const rim2 = m.earFar * m.face;
3486
3499
  const root2 = earRoot(m, face, y, s);
3487
3500
  const lead = Math.max(
3488
3501
  0,
@@ -3507,7 +3520,7 @@ function farEarCovered(m) {
3507
3520
  const ears = m.frame.ears;
3508
3521
  if (ears === void 0) return true;
3509
3522
  const face = faceField(m);
3510
- const rim = TURN.earFar * m.face;
3523
+ const rim = m.earFar * m.face;
3511
3524
  const y0 = ears.bottom - 2;
3512
3525
  const y1 = ears.top + 2;
3513
3526
  const n = Math.max(1, Math.ceil(y1 - y0));
@@ -4348,6 +4361,7 @@ function fitTurn(ctx, targets, plan, build) {
4348
4361
  lead: plan.turn.featureLead,
4349
4362
  follow: plan.hair.follow,
4350
4363
  outline: plan.hair.outlineFollow,
4364
+ earFar: plan.turn.farEar,
4351
4365
  fore: targets.farEyeRatio === void 0 ? plan.turn.eyeForeshorten : 1,
4352
4366
  shell: 1,
4353
4367
  noseLead: targets.noseShift !== void 0 || plan.turn.noseLead === 1 ? void 0 : plan.turn.noseLead
@@ -4584,6 +4598,7 @@ function buildTurn(frame, landmarks, q, turn) {
4584
4598
  hairOuter: q.outline * face,
4585
4599
  hairBack: A * hh * turn.hairBack,
4586
4600
  shellScale: q.shell,
4601
+ earFar: q.earFar,
4587
4602
  earFarScale: 1 + A * (turn.earFarScale - 1),
4588
4603
  ...landmarks
4589
4604
  };
@@ -4671,6 +4686,23 @@ var RIG_PLAN_FIELDS = [
4671
4686
  migratedFrom: null,
4672
4687
  what: "how far the nose leads the face plate on the turn \u2014 turn only, the nod keeps the profile's nose depth"
4673
4688
  },
4689
+ {
4690
+ path: "turn.farEar",
4691
+ group: "turn",
4692
+ key: "farEar",
4693
+ kind: "absolute",
4694
+ unit: "share of the face plate's slide",
4695
+ default: PROFILE.TURN.earFar,
4696
+ range: [0, 1],
4697
+ recommended: [0, 0.5],
4698
+ step: 0.1,
4699
+ poses: TURN_POSES,
4700
+ cue: null,
4701
+ fitBy: null,
4702
+ provisional: false,
4703
+ migratedFrom: null,
4704
+ 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)"
4705
+ },
4674
4706
  {
4675
4707
  path: "hair.follow",
4676
4708
  group: "hair",
@@ -6757,6 +6789,11 @@ function buildFaceMesh(frame, b, head, neckCells) {
6757
6789
  frame.axisX - frame.headMax + 0.5,
6758
6790
  frame.axisX + frame.headMax - 0.5
6759
6791
  ];
6792
+ const ears2 = frame.ears;
6793
+ if (ears2 !== void 0 && neck2 === void 0) {
6794
+ const d = ears2.edgeAt(ears2.bottom);
6795
+ at.push(frame.axisX - d, frame.axisX + d);
6796
+ }
6760
6797
  if (neck2 !== void 0) {
6761
6798
  at.push(
6762
6799
  frame.axisX,