@ikijs/editor 0.12.2 → 0.14.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
@@ -57,23 +57,22 @@ leaves the document untouched.
57
57
  ## Auto-rig
58
58
 
59
59
  `generateIkiFromLayerSet` turns role-named layers (`face`, `eye_L`, `eye_R`,
60
- `mouth`, plus optional `iris_*`, `brow_*`, `lash_*`, `hair_front`, `hair_back`,
60
+ `mouth`, plus optional `iris_*`, `brow_*`, `lash_*`, `lash_lower_*`, `hair_front`, `hair_back`,
61
61
  …) into a rigged model that blinks, gazes, opens its mouth, turns its head over
62
62
  a torso that breathes, and emotes with its brows — including a
63
63
  hair-sway physics rig when a `hair_front` layer is present.
64
64
 
65
- The rig reproduces a Live2D default rig, measured on the Cubism sample models
66
- (the numbers are in `auto-rig/profile.ts`): the turn reads through parallax,
67
- not a reshaped face. The face plate translates, changing its width by a few
68
- percent, the chin leading it; the features lead the plate by their own amounts
69
- (the nose most, then the brows, eyes and mouth), the eyes foreshortening mildly
65
+ The rig turns the head by parallax, not a reshaped face, with values of our
66
+ own picked by eye (`auto-rig/profile.ts`, `AUTO-RIG.md`). The face plate
67
+ translates, the chin leading it; the features lead the plate by their depth
68
+ (the nose most, then the eyes, brows and mouth), the eyes foreshortening mildly
70
69
  about their own centres; the front hair rides the face where it lies over it and holds the head's
71
70
  outline where it draws it — though on its cap the turn never carries its outer
72
71
  edge further outside the back hair painted behind it than the nod alone does —
73
72
  and the back hair stays behind; the neck and the
74
73
  ears the face layer paints are cut off into islands of the plate's own mesh,
75
74
  drawn behind the head — the neck stays (only the chin's shade slides across
76
- it), the ears lag the face, the far one narrowing. AngleZ rolls the head 10° about the chin; the torso only
75
+ it), the ears lag the face, the far one narrowing. AngleZ rolls the head 14° about the chin; the torso only
77
76
  breathes.
78
77
 
79
78
  On a layer set with a `nose`, `options.turnTargets` — the cues a 30° reference
@@ -82,7 +81,7 @@ whether the silhouette holds) — are **fitted** each by the one knob it reads:
82
81
  the turn's amount, the eyes' foreshortening, the hair's width, a feature's own
83
82
  shift. With none given the profile itself renders (`DEFAULT_TURN_TARGETS`
84
83
  expresses it in the cues' units). `options.style` tunes a character from the
85
- profile where the Live2D samples themselves disagree: the whole turn, the
84
+ profile: the whole turn, the
86
85
  features' lead, the front hair's follow and its outline's, the blink and the
87
86
  hair sway. The turn
88
87
  is bounded by the art: the far eye stays inside the plate's outline and, given
package/dist/index.d.mts CHANGED
@@ -657,21 +657,18 @@ interface AlphaBbox {
657
657
  declare function detectAlphaBbox(rgba: ArrayLike<number>, width: number, height: number): AlphaBbox | null;
658
658
 
659
659
  /**
660
- * Per-character tuning, starting from the profile: only the quantities the
661
- * Live2D samples themselves disagree on by more than their own median
662
- * (style, not construction). `turn`, `featureLead` and `sway` multiply the
663
- * profile (1 = as measured); `hairFollow`, `outlineFollow` and `blink`
664
- * replace its value.
660
+ * Per-character tuning, starting from the profile. `turn`, `featureLead` and
661
+ * `sway` multiply the profile (1 = the profile); `hairFollow`,
662
+ * `outlineFollow` and `blink` replace its value. `AUTO-RIG.md` lists the
663
+ * ranges that read well.
665
664
  */
666
665
  interface RigStyle {
667
- /** The whole turn — plate, features and hair (the samples' face slides
668
- * 0.05–0.25 hh). Ignored when a `turnTargets.eyeShift` is given, which
669
- * fits it instead. */
666
+ /** The whole turn — plate, features and hair. Ignored when a
667
+ * `turnTargets.eyeShift` is given, which fits it instead. */
670
668
  turn?: number;
671
- /** How far the features lead the plate (the nose leads it 2.2–4.4×). */
669
+ /** How far the features lead the plate. */
672
670
  featureLead?: number;
673
- /** The front hair's share of the face's turn (the samples' 1.0–1.37;
674
- * profile 1.1). */
671
+ /** The front hair's share of the face's turn. */
675
672
  hairFollow?: number;
676
673
  /** Its outer edge's share, where the front hair draws the head's outline
677
674
  * and no back hair paints behind that edge (profile 0: the outline holds;
@@ -683,9 +680,9 @@ interface RigStyle {
683
680
  * otherwise carry it further outside its back hair than the nod alone
684
681
  * does. */
685
682
  outlineFollow?: number;
686
- /** How far the upper lid comes down, over the eye's height (0.30–0.67). */
683
+ /** How far the upper lid comes down, over the eye's height. */
687
684
  blink?: number;
688
- /** Hair sway amplitude (0.02–0.36 hh at the extremes). */
685
+ /** Hair sway amplitude. */
689
686
  sway?: number;
690
687
  }
691
688
 
@@ -765,12 +762,11 @@ declare class TurnTargetError extends Error {
765
762
  constructor(message: string);
766
763
  }
767
764
  /**
768
- * The Live2D profile's own turn (`profile.ts`), in the cues' units — what a
769
- * rig with no `turnTargets` renders on a head whose silhouette at the eye row
770
- * is back hair about 1 hh (eye row → chin) wide. eyeShift: the eye pair moves
771
- * 0.211 hh at ±30 and that silhouette's centre −0.009 hh, over a 1 hh head
772
- * half-width, 0.22. farEyeRatio: the far eye narrows to 0.85 and the near one
773
- * widens to 1.085, and the cue is their ratio, 0.78. silhouetteRatio: the
765
+ * The profile's own turn (`profile.ts`), in the cues' units — what a rig with
766
+ * no `turnTargets` renders on a head whose silhouette at the eye row is back
767
+ * hair about 1 hh (eye row → chin) wide. eyeShift: the eye pair's mean shift
768
+ * at ±30 less the back hair's drift, over a 1 hh head half-width.
769
+ * farEyeRatio: the far eye's width over the near one's. silhouetteRatio: the
774
770
  * silhouette holds its width.
775
771
  *
776
772
  * With no target given the rig renders the profile itself, whatever the cues
@@ -858,7 +854,7 @@ declare function parseLayerRoles(fileNames: string[]): {
858
854
  /**
859
855
  * `generateIkiFromLayerSet`: role layers in, a rigged `.iki` out.
860
856
  *
861
- * The rig's shape follows a Live2D default rig (`profile.ts`): a torso that
857
+ * The rig's shape is a 2D head rig's usual one (`profile.ts`): a torso that
862
858
  * only breathes; a head deformer that rolls about the chin and breathes; one
863
859
  * small warp grid per feature (each eye, each brow, the nose, the mouth),
864
860
  * translating and foreshortening it by its own lead over the plate; and the
package/dist/index.d.ts CHANGED
@@ -657,21 +657,18 @@ interface AlphaBbox {
657
657
  declare function detectAlphaBbox(rgba: ArrayLike<number>, width: number, height: number): AlphaBbox | null;
658
658
 
659
659
  /**
660
- * Per-character tuning, starting from the profile: only the quantities the
661
- * Live2D samples themselves disagree on by more than their own median
662
- * (style, not construction). `turn`, `featureLead` and `sway` multiply the
663
- * profile (1 = as measured); `hairFollow`, `outlineFollow` and `blink`
664
- * replace its value.
660
+ * Per-character tuning, starting from the profile. `turn`, `featureLead` and
661
+ * `sway` multiply the profile (1 = the profile); `hairFollow`,
662
+ * `outlineFollow` and `blink` replace its value. `AUTO-RIG.md` lists the
663
+ * ranges that read well.
665
664
  */
666
665
  interface RigStyle {
667
- /** The whole turn — plate, features and hair (the samples' face slides
668
- * 0.05–0.25 hh). Ignored when a `turnTargets.eyeShift` is given, which
669
- * fits it instead. */
666
+ /** The whole turn — plate, features and hair. Ignored when a
667
+ * `turnTargets.eyeShift` is given, which fits it instead. */
670
668
  turn?: number;
671
- /** How far the features lead the plate (the nose leads it 2.2–4.4×). */
669
+ /** How far the features lead the plate. */
672
670
  featureLead?: number;
673
- /** The front hair's share of the face's turn (the samples' 1.0–1.37;
674
- * profile 1.1). */
671
+ /** The front hair's share of the face's turn. */
675
672
  hairFollow?: number;
676
673
  /** Its outer edge's share, where the front hair draws the head's outline
677
674
  * and no back hair paints behind that edge (profile 0: the outline holds;
@@ -683,9 +680,9 @@ interface RigStyle {
683
680
  * otherwise carry it further outside its back hair than the nod alone
684
681
  * does. */
685
682
  outlineFollow?: number;
686
- /** How far the upper lid comes down, over the eye's height (0.30–0.67). */
683
+ /** How far the upper lid comes down, over the eye's height. */
687
684
  blink?: number;
688
- /** Hair sway amplitude (0.02–0.36 hh at the extremes). */
685
+ /** Hair sway amplitude. */
689
686
  sway?: number;
690
687
  }
691
688
 
@@ -765,12 +762,11 @@ declare class TurnTargetError extends Error {
765
762
  constructor(message: string);
766
763
  }
767
764
  /**
768
- * The Live2D profile's own turn (`profile.ts`), in the cues' units — what a
769
- * rig with no `turnTargets` renders on a head whose silhouette at the eye row
770
- * is back hair about 1 hh (eye row → chin) wide. eyeShift: the eye pair moves
771
- * 0.211 hh at ±30 and that silhouette's centre −0.009 hh, over a 1 hh head
772
- * half-width, 0.22. farEyeRatio: the far eye narrows to 0.85 and the near one
773
- * widens to 1.085, and the cue is their ratio, 0.78. silhouetteRatio: the
765
+ * The profile's own turn (`profile.ts`), in the cues' units — what a rig with
766
+ * no `turnTargets` renders on a head whose silhouette at the eye row is back
767
+ * hair about 1 hh (eye row → chin) wide. eyeShift: the eye pair's mean shift
768
+ * at ±30 less the back hair's drift, over a 1 hh head half-width.
769
+ * farEyeRatio: the far eye's width over the near one's. silhouetteRatio: the
774
770
  * silhouette holds its width.
775
771
  *
776
772
  * With no target given the rig renders the profile itself, whatever the cues
@@ -858,7 +854,7 @@ declare function parseLayerRoles(fileNames: string[]): {
858
854
  /**
859
855
  * `generateIkiFromLayerSet`: role layers in, a rigged `.iki` out.
860
856
  *
861
- * The rig's shape follows a Live2D default rig (`profile.ts`): a torso that
857
+ * The rig's shape is a 2D head rig's usual one (`profile.ts`): a torso that
862
858
  * only breathes; a head deformer that rolls about the chin and breathes; one
863
859
  * small warp grid per feature (each eye, each brow, the nose, the mouth),
864
860
  * translating and foreshortening it by its own lead over the plate; and the
package/dist/index.js CHANGED
@@ -1963,14 +1963,155 @@ function createDefaultWarpDeformer(model) {
1963
1963
  };
1964
1964
  }
1965
1965
 
1966
+ // src/auto-rig/profile.ts
1967
+ var PROFILE_VALUES = {
1968
+ slide: 0.12,
1969
+ lead: 0.1,
1970
+ noseDepth: 2,
1971
+ hairFollow: 1,
1972
+ fore: 0.2,
1973
+ backShare: 0.1,
1974
+ earFar: 0.5,
1975
+ nodDown: 0.18,
1976
+ nodUp: 0.1,
1977
+ nodLead: 0.06,
1978
+ chinShare: 0.75,
1979
+ rollDeg: 14,
1980
+ gaze: 0.2,
1981
+ blink: 0.6,
1982
+ mouthOpen: 0.8,
1983
+ mouthWiden: 1.2,
1984
+ brow: 0.15,
1985
+ breath: 0.03,
1986
+ sway: 0.08
1987
+ };
1988
+ function deriveProfile(v) {
1989
+ const farLead = v.lead * (1 - v.fore / 2);
1990
+ const nearLead = v.lead * (1 + v.fore / 2);
1991
+ const turn = {
1992
+ /** The face plate's translation. */
1993
+ face: v.slide,
1994
+ /** The chin tip's lead over the plate. */
1995
+ chinLead: v.lead,
1996
+ /** The plate's width at full turn: eye and cheek rows, then the jaw. The
1997
+ * plate translates; it does not narrow. */
1998
+ widthUpper: 1,
1999
+ widthJaw: 1,
2000
+ eyeFar: v.slide + farLead,
2001
+ eyeNear: v.slide + nearLead,
2002
+ /** Each eye's width at full turn, about its own centre. */
2003
+ eyeFarScale: 1 - v.fore,
2004
+ eyeNearScale: 1 + v.fore / 2,
2005
+ browFar: v.slide + farLead,
2006
+ browNear: v.slide + nearLead,
2007
+ nose: v.slide + v.noseDepth * v.lead,
2008
+ mouth: v.slide + v.lead,
2009
+ mouthWidth: 1,
2010
+ /** The mouth's far corner rises (its near end drops) by this much: it
2011
+ * stays level. */
2012
+ mouthTiltDeg: 0,
2013
+ /** The front hair (bangs and side locks) rides the face at this share of
2014
+ * its translation. */
2015
+ hairFollow: v.hairFollow,
2016
+ /** The head's outline at the eye row holds: where the front hair draws
2017
+ * it, its outer edge follows the face this much on a row where no back
2018
+ * hair paints behind that edge. Where back hair does, the edge rides
2019
+ * further, as far as that back hair stays behind it through the turn and
2020
+ * the nod, up to the face's follow there (`hairFollow`; over the far
2021
+ * eye's rows, as far as that eye's outer corner goes). On the cap (the
2022
+ * rows above the eyes) it follows less where the turn would otherwise
2023
+ * carry it further outside its back hair than the nod alone does. */
2024
+ outlineFollow: 0,
2025
+ /** The back hair's slight counter-motion. */
2026
+ hairBack: -v.backShare * v.slide,
2027
+ /** The ears lag the plate: the far one's outer edge moves this share of
2028
+ * its slide; the near one rides the head. */
2029
+ earFar: v.earFar,
2030
+ earNear: 1,
2031
+ /** The far ear's width at full turn, about its outer edge, where the head
2032
+ * leaves it room; less where it does not. */
2033
+ earFarScale: 1 - v.fore
2034
+ };
2035
+ const mid = v.nodDown * (1 + v.chinShare) / 2;
2036
+ const feature = (depth) => ({
2037
+ up: -(v.nodUp + depth * v.nodLead),
2038
+ down: mid + depth * v.nodLead
2039
+ });
2040
+ const nod = {
2041
+ faceTop: { up: -v.nodUp, down: v.nodDown },
2042
+ chin: { up: -v.nodUp, down: v.chinShare * v.nodDown },
2043
+ eye: feature(1),
2044
+ /** Eye height at the extreme, about the eye's centre: it holds. */
2045
+ eyeHeight: { up: 1, down: 1 },
2046
+ brow: feature(1),
2047
+ nose: feature(v.noseDepth),
2048
+ mouth: feature(1),
2049
+ hairFront: { up: -v.hairFollow * v.nodUp, down: v.hairFollow * v.nodDown },
2050
+ /** The front hair's cap top: half the plate's rise looking up; looking
2051
+ * down the plate's top, which the rig slides only as far as the back
2052
+ * hair covers the crown behind it. */
2053
+ hairFrontTop: { up: -v.nodUp / 2, down: v.nodDown },
2054
+ hairBack: { up: -v.backShare * v.nodUp, down: v.backShare * v.nodDown }
2055
+ };
2056
+ const amplitude = {
2057
+ /** Iris travel, in iris widths. */
2058
+ gazeX: v.gaze,
2059
+ gazeY: v.gaze / 2,
2060
+ /** How far the upper lid comes down, over the eye's height. */
2061
+ blink: v.blink,
2062
+ /** A single mouth drawing opens to this height over its rest width. */
2063
+ mouthOpenHeight: v.mouthOpen,
2064
+ /** The mouth widens this much open. */
2065
+ mouthOpenWidth: v.mouthWiden,
2066
+ /** Brow raise / lower, hh. */
2067
+ brow: v.brow,
2068
+ /** Breath lifts the shoulders and the head, hh. */
2069
+ breathBody: v.breath,
2070
+ breathHead: v.breath * 2 / 3,
2071
+ /** Hair tip travel at full sway (±20), hh. */
2072
+ sway: v.sway,
2073
+ /** The neck under the face reaches this far above the jaw, hh — what the
2074
+ * chin uncovers as it slides off it: half again the chin's full-turn
2075
+ * slide, to the next 0.05. */
2076
+ hiddenNeck: Math.ceil(30 * (v.slide + v.lead) - 1e-9) / 20
2077
+ };
2078
+ return { TURN: turn, NOD: nod, ROLL_DEG: v.rollDeg, AMPLITUDE: amplitude };
2079
+ }
2080
+ var { TURN, NOD, ROLL_DEG, AMPLITUDE } = deriveProfile(PROFILE_VALUES);
2081
+ var STYLE_KNOBS = [
2082
+ "turn",
2083
+ "featureLead",
2084
+ "hairFollow",
2085
+ "outlineFollow",
2086
+ "blink",
2087
+ "sway"
2088
+ ];
2089
+ function resolveStyle(style, check) {
2090
+ const s = style ?? {};
2091
+ const get = (key, def, lo, hi) => s[key] === void 0 ? def : check(`style.${key}`, s[key], lo, hi);
2092
+ return {
2093
+ turn: get("turn", 1, 0, 3),
2094
+ featureLead: get("featureLead", 1, 0, 3),
2095
+ hairFollow: get("hairFollow", TURN.hairFollow, 0, 2),
2096
+ outlineFollow: get("outlineFollow", TURN.outlineFollow, 0, 2),
2097
+ blink: get("blink", AMPLITUDE.blink, 0.1, 1),
2098
+ sway: get("sway", 1, 0, 5)
2099
+ };
2100
+ }
2101
+
1966
2102
  // src/auto-rig/types.ts
2103
+ var round2 = (v) => Math.round(v * 100) / 100;
1967
2104
  var TurnTargetError = class extends Error {
1968
2105
  constructor(message) {
1969
2106
  super(message);
1970
2107
  this.name = "TurnTargetError";
1971
2108
  }
1972
2109
  };
1973
- var DEFAULT_TURN_TARGETS = Object.freeze({ eyeShift: 0.22, farEyeRatio: 0.78, silhouetteRatio: 1 });
2110
+ var DEFAULT_TURN_TARGETS = Object.freeze({
2111
+ eyeShift: round2((TURN.eyeFar + TURN.eyeNear) / 2 - TURN.hairBack),
2112
+ farEyeRatio: round2(TURN.eyeFarScale / TURN.eyeNearScale),
2113
+ silhouetteRatio: 1
2114
+ });
1974
2115
 
1975
2116
  // src/auto-rig/roles.ts
1976
2117
  var ROLE_TABLE = [
@@ -1990,6 +2131,8 @@ var ROLE_TABLE = [
1990
2131
  { role: "pupil_R", family: "eye_R" },
1991
2132
  { role: "highlight_L", family: "eye_L" },
1992
2133
  { role: "highlight_R", family: "eye_R" },
2134
+ { role: "lash_lower_L", family: "eye_L" },
2135
+ { role: "lash_lower_R", family: "eye_R" },
1993
2136
  { role: "lash_L", family: "eye_L" },
1994
2137
  { role: "lash_R", family: "eye_R" },
1995
2138
  { role: "brow_L", family: "brow_L" },
@@ -2427,117 +2570,6 @@ function land(l, offsets, x, y) {
2427
2570
  return [topX + (botX - topX) * t, topY + (botY - topY) * t];
2428
2571
  }
2429
2572
 
2430
- // src/auto-rig/profile.ts
2431
- var HH_PER_EYE_TO_CHIN = 1.017;
2432
- var TURN = {
2433
- /** The face plate's translation (its cheek edges, 0.118 / 0.126). */
2434
- face: 0.118,
2435
- /** The chin tip's lead over the plate (it moves 0.194). */
2436
- chinLead: 0.076,
2437
- /** The plate's width at full turn: eye and cheek rows, then the jaw. */
2438
- widthUpper: 0.985,
2439
- widthJaw: 0.96,
2440
- eyeFar: 0.192,
2441
- eyeNear: 0.23,
2442
- /** Each eye's width at full turn, about its own centre. */
2443
- eyeFarScale: 0.85,
2444
- eyeNearScale: 1.085,
2445
- browFar: 0.213,
2446
- browNear: 0.243,
2447
- nose: 0.301,
2448
- mouth: 0.213,
2449
- mouthWidth: 0.975,
2450
- /** The mouth's far corner rises (its near end drops) by this much. */
2451
- mouthTiltDeg: 4.4,
2452
- /** The front hair (bangs and side locks) rides the face at this share of
2453
- * its translation (bangs 1.18×, side locks at the eye row ≈ 1.0×). */
2454
- hairFollow: 1.1,
2455
- /** The head's outline at the eye row holds (−0.014 / −0.004 hh): where the
2456
- * front hair draws it, its outer edge follows the face this much on a row
2457
- * where no back hair paints behind that edge. Where back hair does, the
2458
- * edge rides further, as far as that back hair stays behind it through
2459
- * the turn and the nod, up to the face's follow there (`hairFollow`;
2460
- * over the far eye's rows, as far as that eye's outer corner goes). On
2461
- * the cap (the rows above the eyes) it follows less where the turn would
2462
- * otherwise carry it further outside its back hair than the nod alone
2463
- * does. */
2464
- outlineFollow: 0,
2465
- /** The back hair's slight counter-motion. */
2466
- hairBack: -0.027,
2467
- /** The ears lag the plate: the far one's outer edge moves this share of
2468
- * its slide, the near one this at its widest reach, its root riding the
2469
- * head (the samples' parallax ratios, 0.44 and 0.87). */
2470
- earFar: 0.44,
2471
- earNear: 0.87,
2472
- /** The far ear's width at full turn, about its outer edge, where the head
2473
- * leaves it room (the samples' median: Haru 0.81 / 0.91, Hiyori 0.90 /
2474
- * 0.85, Mao 0.77 / 0.79; none fades); less where it does not. */
2475
- earFarScale: 0.83
2476
- };
2477
- var NOD = {
2478
- faceTop: { up: -0.081, down: 0.178 },
2479
- chin: { up: -0.0875, down: 0.109 },
2480
- eye: { up: -0.167, down: 0.202 },
2481
- /** Eye height at the extreme, about the eye's centre. */
2482
- eyeHeight: { up: 0.994, down: 0.96 },
2483
- brow: { up: -0.167, down: 0.223 },
2484
- nose: { up: -0.207, down: 0.193 },
2485
- mouth: { up: -0.172, down: 0.177 },
2486
- hairFront: { up: -0.11, down: 0.189 },
2487
- /** The front hair's cap top. The samples' cap top moves 1.215× their face's
2488
- * centroid looking down (Mao 1.236, Haru 1.626, Hiyori 1.194, Natori
2489
- * 0.730) and 0.415× looking up (0.385, 0.430, 0.400, 0.487); each ratio
2490
- * times the plate's own nod halfway between its top and the chin (0.1435
2491
- * down, 0.084 up). Looking down, the rig slides it only as far as the
2492
- * back hair covers the crown behind it. */
2493
- hairFrontTop: { up: -0.035, down: 0.174 },
2494
- hairBack: { up: -0.011, down: 0.025 }
2495
- };
2496
- var ROLL_DEG = 10;
2497
- var AMPLITUDE = {
2498
- /** Iris travel, in iris widths (X 0.045 hh, Y 0.031 hh on the samples). */
2499
- gazeX: 0.17,
2500
- gazeY: 0.11,
2501
- /** How far the upper lid comes down, over the eye's height (Haru 0.30,
2502
- * Mao 0.58, Hiyori 0.67). */
2503
- blink: 0.58,
2504
- /** A single mouth drawing opens to this height over its rest width. */
2505
- mouthOpenHeight: 0.77,
2506
- /** The mouth widens this much open. */
2507
- mouthOpenWidth: 1.15,
2508
- /** Brow raise / lower, hh. */
2509
- brow: 0.089,
2510
- /** Breath lifts the shoulders and the head, hh. */
2511
- breathBody: 0.025,
2512
- breathHead: 0.016,
2513
- /** Hair tip travel at full sway (±20), hh (the keyform ranges at ±1 have a
2514
- * median of 0.05–0.06 hh). */
2515
- sway: 0.06,
2516
- /** The neck under the face reaches this far above the jaw, hh — what the
2517
- * chin uncovers as it slides off it. */
2518
- hiddenNeck: 0.3
2519
- };
2520
- var STYLE_KNOBS = [
2521
- "turn",
2522
- "featureLead",
2523
- "hairFollow",
2524
- "outlineFollow",
2525
- "blink",
2526
- "sway"
2527
- ];
2528
- function resolveStyle(style, check) {
2529
- const s = style ?? {};
2530
- const get = (key, def, lo, hi) => s[key] === void 0 ? def : check(`style.${key}`, s[key], lo, hi);
2531
- return {
2532
- turn: get("turn", 1, 0, 3),
2533
- featureLead: get("featureLead", 1, 0, 3),
2534
- hairFollow: get("hairFollow", TURN.hairFollow, 0, 2),
2535
- outlineFollow: get("outlineFollow", TURN.outlineFollow, 0, 2),
2536
- blink: get("blink", AMPLITUDE.blink, 0.1, 1),
2537
- sway: get("sway", 1, 0, 5)
2538
- };
2539
- }
2540
-
2541
2573
  // src/auto-rig/head.ts
2542
2574
  function outerEdge(runs, axisX, side) {
2543
2575
  if (runs.length === 0) return void 0;
@@ -2750,7 +2782,7 @@ function buildHeadFrame(layers, opts) {
2750
2782
  return face.y1;
2751
2783
  };
2752
2784
  const chin = chinY;
2753
- const shade = JAW_SHADE * HH_PER_EYE_TO_CHIN * Math.max(1, eyeY - chin);
2785
+ const shade = JAW_SHADE * Math.max(1, eyeY - chin);
2754
2786
  const reach = opts.chinSlide === void 0 ? nk.waist : clamp(
2755
2787
  nk.waist - opts.chinSlide - CUT_MARGIN - (CUT_LOW + 1),
2756
2788
  0,
@@ -2799,7 +2831,7 @@ function buildHeadFrame(layers, opts) {
2799
2831
  }
2800
2832
  }
2801
2833
  }
2802
- const hh = HH_PER_EYE_TO_CHIN * Math.max(1, eyeY - chinY);
2834
+ const hh = Math.max(1, eyeY - chinY);
2803
2835
  const headHalf = opts.headHalfWidth;
2804
2836
  const hairShell = headHalf !== void 0;
2805
2837
  let shellLeft = axisX - plateHalf;
@@ -4428,12 +4460,12 @@ function motionReach(warps, mesh, box, extra = [0, 0]) {
4428
4460
  }
4429
4461
  return out;
4430
4462
  }
4431
- function blinkFold(param, mesh, eye, travel) {
4463
+ function blinkFold(param, mesh, box, travel, eye = box) {
4432
4464
  const yc = eye.y1 - travel * bh(eye);
4433
4465
  return localWarp(
4434
4466
  param,
4435
4467
  mesh,
4436
- eye,
4468
+ box,
4437
4469
  [0, 1],
4438
4470
  ([, y], v) => v === 1 ? [0, 0] : [0, yc - y]
4439
4471
  );
@@ -4782,6 +4814,10 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
4782
4814
  case "eye_R":
4783
4815
  warps.push(blinkFold(eyeOpen, mesh, b, style.blink));
4784
4816
  break;
4817
+ case "lash_lower_L":
4818
+ case "lash_lower_R":
4819
+ warps.push(blinkFold(eyeOpen, mesh, b, style.blink, box(`eye_${side}`)));
4820
+ break;
4785
4821
  case "lash_L":
4786
4822
  case "lash_R":
4787
4823
  warps.push(lashFold(eyeOpen, mesh, b, box(`eye_${side}`), style.blink));