@ikijs/editor 0.22.0 → 0.24.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,7 +57,7 @@ leaves the document untouched.
57
57
  ## Auto-rig
58
58
 
59
59
  `generateIkiFromLayerSet` turns role-named layers (`face`, `eye_L`, `eye_R`,
60
- and `mouth` — or the lip set `mouth_inner`, `lip_lower`, `lip_upper`, which folds open instead of crossfading — plus optional `iris_*`, `brow_*`, `lash_*`, `lash_lower_*`, `hair_front`, `hair_back`,
60
+ and `mouth` — or the lip set `mouth_inner`, `lip_lower`, `lip_upper`, which folds open instead of crossfading, with optional `mouth_tongue` / `mouth_teeth` clipped to `mouth_inner` (only with the lip set) — plus optional `iris_*`, `brow_*`, `lash_*`, `lash_lower_*`, `hair_front`, `hair_back`,
61
61
  `body`, `arm_*`, …) into a rigged model that blinks, gazes, opens its mouth, turns its head over
62
62
  a body warp that breathes, follows the head a little and turns on
63
63
  `ParamBodyAngleX/Y/Z`, and emotes with its brows — including a
package/dist/index.d.mts CHANGED
@@ -891,9 +891,12 @@ interface GenerateOptions {
891
891
  * wedge carries the painted line, fading out in the antialias column as the
892
892
  * painted edge does; the fan inside it is the crop's own pixels.
893
893
  *
894
- * Draw order: hair_back < body < elbow_L, arm_L, forearm_L, elbow_R, arm_R,
895
- * forearm_R < face … < hair_front. The forearm draws over the upper arm,
896
- * which draws over the cap.
894
+ * Draw order: hair_back < body < elbow_L, forearm_L, arm_L, elbow_R,
895
+ * forearm_R, arm_R < face … < hair_front. The upper arm draws over the
896
+ * forearm, which draws over the cap. On the inside of a bend the bands
897
+ * overlap, and the forearm's seam-row cut stays under the upper arm there, so
898
+ * the overlap shows the upper arm's bottom edge at the elbow row instead of
899
+ * the cut rotated up the sleeve.
897
900
  *
898
901
  * Signs: ParamArmX + raises the arm outward on either side, in degrees 1:1.
899
902
  * A rotation is CCW-positive, so an arm whose shoulder lies right of the
@@ -996,14 +999,19 @@ declare function forearmPoseGeometry(layer: LayerInput): ForearmPoseGeometry;
996
999
  /** The lip set's roles, back to front (`mouth.ts`). */
997
1000
  declare const LIP_ROLES: readonly ["mouth_inner", "lip_lower", "lip_upper"];
998
1001
  type LipRole = (typeof LIP_ROLES)[number];
1002
+ /** The lip set's optional inside parts, back to front: cut from the interior
1003
+ * and clipped to `mouth_inner` (`mouth.ts`). */
1004
+ declare const LIP_INSIDE_ROLES: readonly ["mouth_tongue", "mouth_teeth"];
1005
+ type LipInsideRole = (typeof LIP_INSIDE_ROLES)[number];
1006
+ declare const isLipInsideRole: (role: string) => role is LipInsideRole;
999
1007
  /** The part ids a `role`'s layer becomes, back to front: every one of them
1000
1008
  * takes its texture from the layer's crop. Throws on an unknown role. */
1001
1009
  declare function partIdsOfRole(role: string): readonly string[];
1002
1010
  /**
1003
1011
  * Map file names to roles. Throws on an unknown role, a role named twice, a
1004
1012
  * required role (`face`, `eye_L`, `eye_R`) missing, no mouth (`mouth`, or the
1005
- * lip set) or a partial or mixed one, an arm without a body, or a pose forearm
1006
- * without its arm.
1013
+ * lip set) or a partial or mixed one, a lip set's inside part without the set,
1014
+ * an arm without a body, or a pose forearm without its arm.
1007
1015
  */
1008
1016
  declare function parseLayerRoles(fileNames: string[]): {
1009
1017
  role: string;
@@ -1014,7 +1022,9 @@ declare function parseLayerRoles(fileNames: string[]): {
1014
1022
  * The lip set: a mouth that folds open the way the eyelid folds shut. Three
1015
1023
  * parts, back to front: `mouth_inner` (the interior and the opening's lower
1016
1024
  * outline band), `lip_lower` (the lower lip's skin) and `lip_upper` (the upper
1017
- * lip line and the corner hooks).
1025
+ * lip line and the corner hooks). Between the interior and the skin it may
1026
+ * carry two inside parts, `mouth_tongue` then `mouth_teeth`, cut from the
1027
+ * interior and clipped to it: the rig reads nothing off them but their box.
1018
1028
  *
1019
1029
  * What the rig reads is the layers' own alpha edges, as exclusive run
1020
1030
  * boundaries in model y (+y up; a run of crop rows `[a, b)` is the span
@@ -1028,43 +1038,75 @@ declare function parseLayerRoles(fileNames: string[]): {
1028
1038
  * them at rest) and the skin reaches the interior (`Bl >= Bb`; an overlap is
1029
1039
  * fine, a gap is not).
1030
1040
  *
1031
- * MouthOpen 0 folds shut onto a seam, 1 is the art as drawn (the widening of
1032
- * `mouthWiden` apart). The line comes down until its bottom edge is on the
1033
- * seam, the skin until its top is one overlap `w'` up under the line (`w'` is
1034
- * the stroke `w`, the line's height at the opening's centre, capped by the
1035
- * line's own ink at that column so tucked skin never shows above it), and the
1036
- * interior's bottom rides the skin's top, so the band is never covered. The
1037
- * interior's top closes onto the same edge, `S + w'`: the whole column scales
1038
- * by `v` about it, so shut its height is zero (a degenerate triangle draws
1039
- * nothing, at every column and between knots; an inverted sliver would leak
1040
- * through texture filtering where the line is thin), and its top sits
1041
- * `w'(1 - v)` above the line's bottom edge, under the line's ink, so the slit
1042
- * between the line and the skin is always backed. The slit opens once
1043
- * `v * H > w'(1 - v)`; that dead zone is accepted: lip-sync noise near 0 does
1044
- * not flicker through it.
1041
+ * The line is thinned at every key: its column is squashed toward its bottom
1042
+ * edge by `UPPER_THIN` (`Tu` in the opening, the line's own bottom past it),
1043
+ * one scale per column so the mesh, linear between its rows, keeps that edge
1044
+ * where it was drawn. The thin belongs to the stack above the line's bottom,
1045
+ * not to the line alone: the contract lets the interior reach up inside the
1046
+ * line's ink (`Tu <= T <= lineTop`), and a line thinned over it would uncover
1047
+ * it under its old top, so the interior's rows above `Tu` come down by the
1048
+ * same scale. The fold below acts on the thinned positions.
1045
1049
  *
1046
- * The seam is the line's drawn bottom edge `Tu` plus one smooth closing
1047
- * travel `D`, a cubic in the column index pinned to zero at the opening's two
1048
- * end columns, so the closed line's ends are the drawn line's own ends. It is
1049
- * fitted by least squares to the raw per-column travel `(T - UPPER_SHARE * H)
1050
- * - Tu`, how far the line's bottom is from the share's point in the
1051
- * interior's span. The travel is fitted, not the seam, because `Tu` is an
1052
- * integer-row read: a smooth seam minus that staircase is a sawtooth the mesh
1053
- * would carry into the rendered line, while a smooth travel carries the
1054
- * drawn stroke (whose antialiased edge is smooth) as one piece. The share sets
1055
- * the travel's depth (the sag), the art's two arcs its shape; the cubic keeps
1056
- * an asymmetric mouth's tilt and cannot wobble.
1050
+ * MouthOpen 0 folds shut onto a seam, 1 is the art as drawn and thinned (the
1051
+ * widening of `mouthWiden` apart). The line comes down until its bottom edge
1052
+ * is on the seam, the skin until its top is one overlap `w'` up under the
1053
+ * line, and the interior's bottom rides the skin's top, so the band is never
1054
+ * covered. The interior's top closes onto the same edge, `S + w'`: the whole
1055
+ * column scales by `v` about it, so shut its height is zero (a degenerate
1056
+ * triangle draws nothing, at every column and between knots; an inverted
1057
+ * sliver would leak through texture filtering where the line is thin), and
1058
+ * its top sits `w'(1 - v)` above the line's bottom edge, under the line's ink,
1059
+ * so the slit between the line and the skin is always backed. The slit opens
1060
+ * once `v * H > w'(1 - v)`; that dead zone is accepted: lip-sync noise near 0
1061
+ * does not flicker through it.
1057
1062
  *
1058
- * A lip's columns outside the opening (the corner hooks) do not fold; only
1059
- * the interior's edge columns read the nearest opening column.
1063
+ * Shut, the line is shaped the way an artist draws a closed mouth: from
1064
+ * `END_TAPER_FROM` of the opening's half-width out, each column is squashed
1065
+ * about its thinned centre row, down to `END_TAPER` of its height at the
1066
+ * line's box edge, so the ends taper; the drawn flick past a corner is pulled
1067
+ * toward it (`FLICK_PULL`) and squashed to `FLICK_SQUASH`. Every closed-key
1068
+ * shape is linear in `1 - v`. `w'` therefore follows the shaped line, not the
1069
+ * drawn one: it is measured up from the closed line's bottom edge (which the
1070
+ * taper raises by half the height it takes off) by the stroke `w` (the line's
1071
+ * height at the opening's centre), capped by the column's own ink and thinned
1072
+ * and squashed with the line. The skin's top and the interior's closing point
1073
+ * then stay inside the line's closed ink at every column, the tapered ends
1074
+ * included, and tucked skin never shows above the line.
1060
1075
  *
1061
- * Knots: the three parts share column knots every `MOUTH_KNOT_PX` plus the
1076
+ * Shut, the line is a smooth parabola across the opening. Its chord joins the
1077
+ * line's smoothed bottom (below) at the two end columns; its depth below the
1078
+ * chord, half way, is the sag at the centre column of the drawn arc plus a
1079
+ * fitted closing travel: a cubic in the column index, pinned to zero at the
1080
+ * end columns and fitted by least squares to the raw per-column travel
1081
+ * `(T - UPPER_SHARE * H) - Tu`, how far the line's bottom is from the share's
1082
+ * point in the interior's span. That sag is capped at `CLOSED_SAG_MAX` of the
1083
+ * opening's width, the drawn arc included, so a deep opening closes into a
1084
+ * gentle smile and a line drawn deeper than the cap closes flatter than
1085
+ * drawn. The seam is `Tu` plus the column's travel: the parabola minus the
1086
+ * line's bottom averaged over 3 columns each side (the smoothed bottom), not
1087
+ * minus `Tu` itself, so the end columns do not travel. `Tu` is an integer-row read, and a curve minus that staircase
1088
+ * is a sawtooth the mesh would carry into the rendered line; against the
1089
+ * smoothed bottom the stroke (whose antialiased edge is smooth) moves as one
1090
+ * piece, and a kink drawn in it (a V at the centre) keeps only its offset
1091
+ * from that average, which the cap counts in. Every closed shape and the
1092
+ * preview are read off the seam.
1093
+ *
1094
+ * The lower lip's columns outside the opening (the corner hooks) do not fold,
1095
+ * the line's are only thinned and shaped (no travel), and the interior's edge
1096
+ * columns read the nearest opening column.
1097
+ *
1098
+ * The inside parts move rigidly per column, after the stack's thin: the teeth
1099
+ * by the line's travel, so they ride under its bottom edge (no taper: the
1100
+ * closed interior clips them away), the tongue by the interior's bottom-edge
1101
+ * travel, so it rides the opening's bottom.
1102
+ *
1103
+ * Knots: the parts share column knots every `MOUTH_KNOT_PX` plus the
1062
1104
  * opening's boundaries `x0`, `x1` and `x1 + 1`, each an integer canvas
1063
- * boundary. The fit's x is the column index, which is the mesh's boundary
1105
+ * boundary. The travel's x is the column index, which is the mesh's boundary
1064
1106
  * position: a knot at boundary `b` samples column `b`, so the mesh renders the
1065
- * fit exactly at its knots and the pinned ends render as pinned (`x1` and
1107
+ * travel exactly at its knots and the pinned ends render as pinned (`x1` and
1066
1108
  * `x1 + 1` both sample the last column; the second sits at its right edge).
1067
- * The preview shifts each column by the field at the column's centre
1109
+ * The preview resamples each column through the lip meshes' own landed rows
1068
1110
  * (`mouthRestShift`). A part's mesh rounds its local x, so one knot
1069
1111
  * reconstructs to slightly different model x per part; the fold therefore
1070
1112
  * reads the knot itself, by vertex index, and maps every knot through one rule
@@ -1093,10 +1135,11 @@ interface OpeningColumn {
1093
1135
  Bl: number;
1094
1136
  /** The interior's span. */
1095
1137
  H: number;
1096
- /** How far the skin tucks up under the line. */
1138
+ /** How far the skin tucks up under the line shut: inside the thinned,
1139
+ * squashed line's ink. */
1097
1140
  overlap: number;
1098
- /** Where the lips meet when shut: the line's bottom edge after its smooth
1099
- * closing travel. */
1141
+ /** Where the lips meet when shut: the line's bottom edge after its closing
1142
+ * travel. */
1100
1143
  seam: number;
1101
1144
  }
1102
1145
  interface Opening {
@@ -1110,23 +1153,44 @@ interface Opening {
1110
1153
  w: number;
1111
1154
  /** A column's values; outside `x0..x1`, or in a gap, the nearest one's. */
1112
1155
  at(col: number): OpeningColumn;
1156
+ /** The upper line at a column of `lip_upper`, model y: inside the opening
1157
+ * the run the contract reads (`Tu`, `lineTop`) and the closed taper's
1158
+ * squash; past it (the drawn flick) the column's own ink span and
1159
+ * `FLICK_SQUASH`; `undefined` where `lip_upper` has no run. */
1160
+ line(col: number): {
1161
+ bottom: number;
1162
+ top: number;
1163
+ squash: number;
1164
+ } | undefined;
1113
1165
  }
1114
1166
  declare function mouthOpening(byRole: Map<string, LayerInput>): Opening;
1115
1167
  /**
1116
- * dy of a model-y rest position at pixel column `col` for MouthOpen `v`
1117
- * (0 shut, 1 as drawn). A lip outside the opening moves 0; `mouth_inner`
1168
+ * `[dx, dy]` of a model-y rest position at pixel column `col` for MouthOpen
1169
+ * `v` (0 shut, 1 as drawn), the thin first at every `v` and the fold on the
1170
+ * thinned position. `lip_upper`'s column is squashed toward its line's bottom;
1171
+ * shut, squashed by its `squash` about the thinned centre row and carried by
1172
+ * the travel, or past the opening (the drawn flick, which now moves too)
1173
+ * squashed and pulled toward the corner. `lip_lower` moves by its travel in
1174
+ * the opening and 0 outside it. `mouth_inner`, its rows above `Tu` thinned,
1118
1175
  * scales its column by `v` about `S + w'`, which carries its top and bottom
1119
1176
  * edges along the line and the skin and needs no read of its own height.
1177
+ * `mouth_teeth`, its rows above `Tu` thinned, moves by the line's travel and
1178
+ * `mouth_tongue` by the interior's bottom edge's, each one number per column.
1120
1179
  */
1121
- declare function mouthFold(role: LipRole, opening: Opening): (col: number, y: number, v: number) => number;
1122
- /** A lip's translate at MouthOpen 0 per canvas column as the mesh renders it,
1123
- * for the composer's closed preview: the linear interpolation at the column's
1124
- * centre between the STORED offsets of the part's two bracketing knots (the
1125
- * numbers the `.iki` carries, rounded as `localWarp` rounds them), 0 outside
1126
- * the part's box. `byRole` must be the layers as the rig measures them (the
1127
- * canvas-sized ones): a box measured on a smaller frame clamps differently,
1128
- * and the knot grid starts at the union's edge. */
1129
- declare function mouthRestShift(byRole: Map<string, LayerInput>): (role: "lip_upper" | "lip_lower", col: number) => number;
1180
+ declare function mouthFold(role: LipRole | LipInsideRole, opening: Opening): (col: number, y: number, v: number) => [number, number];
1181
+ /** A lip's dy at MouthOpen 0 at a canvas pixel `(col, row)` as the mesh
1182
+ * renders it, for the composer's closed preview: read at the pixel's centre
1183
+ * off the STORED offsets (the numbers the `.iki` carries, rounded as
1184
+ * `localWarp` rounds them) of the `columnMesh` triangle containing it — the
1185
+ * cell by knot and row, its triangle by the cell's top-left–bottom-right
1186
+ * diagonal, the three vertices weighted barycentrically. The thin and the
1187
+ * taper move a column's rows by different amounts and the renderer is
1188
+ * linear over a triangle, never bilinear over a cell, so no one row stands
1189
+ * for the column. `undefined` for a pixel centre outside the part's box (the
1190
+ * mesh's domain), which has no field. `byRole` must be the layers as the rig
1191
+ * measures them (the canvas-sized ones): a box measured on a smaller frame
1192
+ * clamps differently, and the knot grid starts at the union's edge. */
1193
+ declare function mouthRestShift(byRole: Map<string, LayerInput>): (role: "lip_upper" | "lip_lower", col: number, row: number) => number | undefined;
1130
1194
 
1131
1195
  /**
1132
1196
  * `generateIkiFromLayerSet`: role layers in, a rigged `.iki` out.
@@ -1147,7 +1211,8 @@ declare function mouthRestShift(byRole: Map<string, LayerInput>): (role: "lip_up
1147
1211
  * the default expressions and the Nod, Shake and Tilt motions, and
1148
1212
  * with a pose forearm the Wave (`animations.ts`). The mouth has two paths,
1149
1213
  * chosen by the layers: `mouth` alone stretches, with `mouth_open` it
1150
- * crossfades; the lip set folds open like the eyelid (`mouth.ts`).
1214
+ * crossfades; the lip set folds open like the eyelid (`mouth.ts`), its
1215
+ * optional `mouth_tongue` and `mouth_teeth` clipped to its interior.
1151
1216
  */
1152
1217
 
1153
1218
  declare function generateIkiFromLayerSet(layers: LayerInput[], canvas: {
@@ -1451,10 +1516,17 @@ interface AtlasLayout {
1451
1516
  * BOTTOM only. Page left/top edges need no gutter.
1452
1517
  *
1453
1518
  * pageWidth/pageHeight = tight bound (max x+width+padding, max y+height+padding).
1519
+ * When `maxSide` is given and the first layout is taller than it but not wider,
1520
+ * the pack is retried at every width from the widest padded source up to `maxSide`, and the first
1521
+ * layout that fits the height wins (shelf height is not monotonic in width);
1522
+ * when none does the first is returned for the caller to refuse. The attempts
1523
+ * are every width for this shelf order, not a proof that no packing exists.
1524
+ *
1454
1525
  * Empty sources → { pageWidth: 0, pageHeight: 0, placements: [], padding }.
1455
- * Throws a plain Error naming the offending source id on a non-finite or <= 0 dimension.
1526
+ * Throws a plain Error naming the offending source id on a non-finite or <= 0 dimension,
1527
+ * and on a supplied `maxSide` that is non-finite or <= 0.
1456
1528
  */
1457
- declare function packAtlas(sources: AtlasSource[], padding?: number): AtlasLayout;
1529
+ declare function packAtlas(sources: AtlasSource[], padding?: number, maxSide?: number): AtlasLayout;
1458
1530
  /**
1459
1531
  * Convert a pixel placement into a UV rect, inset by `insetPx` on all four
1460
1532
  * edges and clamped to [0, 1] so the validator's bounds check always passes.
@@ -1545,4 +1617,4 @@ declare function createGridMesh(cols: number, rows: number): IkiMesh;
1545
1617
  */
1546
1618
  declare function createDefaultWarpDeformer(model: IkiModel): IkiWarpDeformer;
1547
1619
 
1548
- export { ALPHA_BBOX_THRESHOLD, ALPHA_OPAQUE, ATLAS_PADDING, AddDeformer, AddPart, AddPhysicsRig, type AlphaBbox, type ApplyAtlasInput, type ArmGeometry, type AtlasAssignment, type AtlasLayout, type AtlasPlacement, type AtlasSource, CaptureGridKeyform, DEFAULT_TURN_TARGETS, type DeformerTransformChannel, DeleteDeformer, DeletePart, DeletePhysicsRig, type EditCommand, type EditTransformChannel, EditorDocument, type ForearmPoseGeometry, HEAD_BAND, type IrisStrand, LIP_ROLES, LayerGeometryError, type LayerInput, type LayerSetMeasurement, type LayerSetMeasurer, type LipRole, type Opening, type OpeningColumn, type RgbaLayer, type RigStyle, SPECK_CORE_FRACTION, SetDeformerBindings, SetDeformerParent, SetDeformerPivot, SetDeformerPivotX, SetDeformerPivotY, SetDeformerTransform, SetPartBindings, SetPartColor, SetPartDeformer, SetPartHeight, SetPartMesh, SetPartOrder, SetPartTransform, SetPartWidth, SetPhysicsRig, type StrandOverlap, type TurnDepths, type TurnSolveReport, TurnTargetError, type TurnTargets, UV_INSET_PX, armGeometry, captureBindingEndpoint, columnRuns, computeGridOffsets, createDefaultMatrixDeformer, createDefaultPart, createDefaultWarpDeformer, createGridMesh, createLayerSetMeasurer, denseCoreOf, detectAlphaBbox, forearmPoseGeometry, foregroundSpan, generateIkiFromLayerSet, headHalfOf, interpolateGridOffsets, isSpeckCore, mouthFold, mouthOpening, mouthRestShift, packAtlas, parseLayerRoles, partIdsOfRole, upsertGridKeyform, uvRectFor, validateDeformerDelete, validateDeformerReparent, validatePartAttach };
1620
+ export { ALPHA_BBOX_THRESHOLD, ALPHA_OPAQUE, ATLAS_PADDING, AddDeformer, AddPart, AddPhysicsRig, type AlphaBbox, type ApplyAtlasInput, type ArmGeometry, type AtlasAssignment, type AtlasLayout, type AtlasPlacement, type AtlasSource, CaptureGridKeyform, DEFAULT_TURN_TARGETS, type DeformerTransformChannel, DeleteDeformer, DeletePart, DeletePhysicsRig, type EditCommand, type EditTransformChannel, EditorDocument, type ForearmPoseGeometry, HEAD_BAND, type IrisStrand, LIP_INSIDE_ROLES, LIP_ROLES, LayerGeometryError, type LayerInput, type LayerSetMeasurement, type LayerSetMeasurer, type LipInsideRole, type LipRole, type Opening, type OpeningColumn, type RgbaLayer, type RigStyle, SPECK_CORE_FRACTION, SetDeformerBindings, SetDeformerParent, SetDeformerPivot, SetDeformerPivotX, SetDeformerPivotY, SetDeformerTransform, SetPartBindings, SetPartColor, SetPartDeformer, SetPartHeight, SetPartMesh, SetPartOrder, SetPartTransform, SetPartWidth, SetPhysicsRig, type StrandOverlap, type TurnDepths, type TurnSolveReport, TurnTargetError, type TurnTargets, UV_INSET_PX, armGeometry, captureBindingEndpoint, columnRuns, computeGridOffsets, createDefaultMatrixDeformer, createDefaultPart, createDefaultWarpDeformer, createGridMesh, createLayerSetMeasurer, denseCoreOf, detectAlphaBbox, forearmPoseGeometry, foregroundSpan, generateIkiFromLayerSet, headHalfOf, interpolateGridOffsets, isLipInsideRole, isSpeckCore, mouthFold, mouthOpening, mouthRestShift, packAtlas, parseLayerRoles, partIdsOfRole, upsertGridKeyform, uvRectFor, validateDeformerDelete, validateDeformerReparent, validatePartAttach };
package/dist/index.d.ts CHANGED
@@ -891,9 +891,12 @@ interface GenerateOptions {
891
891
  * wedge carries the painted line, fading out in the antialias column as the
892
892
  * painted edge does; the fan inside it is the crop's own pixels.
893
893
  *
894
- * Draw order: hair_back < body < elbow_L, arm_L, forearm_L, elbow_R, arm_R,
895
- * forearm_R < face … < hair_front. The forearm draws over the upper arm,
896
- * which draws over the cap.
894
+ * Draw order: hair_back < body < elbow_L, forearm_L, arm_L, elbow_R,
895
+ * forearm_R, arm_R < face … < hair_front. The upper arm draws over the
896
+ * forearm, which draws over the cap. On the inside of a bend the bands
897
+ * overlap, and the forearm's seam-row cut stays under the upper arm there, so
898
+ * the overlap shows the upper arm's bottom edge at the elbow row instead of
899
+ * the cut rotated up the sleeve.
897
900
  *
898
901
  * Signs: ParamArmX + raises the arm outward on either side, in degrees 1:1.
899
902
  * A rotation is CCW-positive, so an arm whose shoulder lies right of the
@@ -996,14 +999,19 @@ declare function forearmPoseGeometry(layer: LayerInput): ForearmPoseGeometry;
996
999
  /** The lip set's roles, back to front (`mouth.ts`). */
997
1000
  declare const LIP_ROLES: readonly ["mouth_inner", "lip_lower", "lip_upper"];
998
1001
  type LipRole = (typeof LIP_ROLES)[number];
1002
+ /** The lip set's optional inside parts, back to front: cut from the interior
1003
+ * and clipped to `mouth_inner` (`mouth.ts`). */
1004
+ declare const LIP_INSIDE_ROLES: readonly ["mouth_tongue", "mouth_teeth"];
1005
+ type LipInsideRole = (typeof LIP_INSIDE_ROLES)[number];
1006
+ declare const isLipInsideRole: (role: string) => role is LipInsideRole;
999
1007
  /** The part ids a `role`'s layer becomes, back to front: every one of them
1000
1008
  * takes its texture from the layer's crop. Throws on an unknown role. */
1001
1009
  declare function partIdsOfRole(role: string): readonly string[];
1002
1010
  /**
1003
1011
  * Map file names to roles. Throws on an unknown role, a role named twice, a
1004
1012
  * required role (`face`, `eye_L`, `eye_R`) missing, no mouth (`mouth`, or the
1005
- * lip set) or a partial or mixed one, an arm without a body, or a pose forearm
1006
- * without its arm.
1013
+ * lip set) or a partial or mixed one, a lip set's inside part without the set,
1014
+ * an arm without a body, or a pose forearm without its arm.
1007
1015
  */
1008
1016
  declare function parseLayerRoles(fileNames: string[]): {
1009
1017
  role: string;
@@ -1014,7 +1022,9 @@ declare function parseLayerRoles(fileNames: string[]): {
1014
1022
  * The lip set: a mouth that folds open the way the eyelid folds shut. Three
1015
1023
  * parts, back to front: `mouth_inner` (the interior and the opening's lower
1016
1024
  * outline band), `lip_lower` (the lower lip's skin) and `lip_upper` (the upper
1017
- * lip line and the corner hooks).
1025
+ * lip line and the corner hooks). Between the interior and the skin it may
1026
+ * carry two inside parts, `mouth_tongue` then `mouth_teeth`, cut from the
1027
+ * interior and clipped to it: the rig reads nothing off them but their box.
1018
1028
  *
1019
1029
  * What the rig reads is the layers' own alpha edges, as exclusive run
1020
1030
  * boundaries in model y (+y up; a run of crop rows `[a, b)` is the span
@@ -1028,43 +1038,75 @@ declare function parseLayerRoles(fileNames: string[]): {
1028
1038
  * them at rest) and the skin reaches the interior (`Bl >= Bb`; an overlap is
1029
1039
  * fine, a gap is not).
1030
1040
  *
1031
- * MouthOpen 0 folds shut onto a seam, 1 is the art as drawn (the widening of
1032
- * `mouthWiden` apart). The line comes down until its bottom edge is on the
1033
- * seam, the skin until its top is one overlap `w'` up under the line (`w'` is
1034
- * the stroke `w`, the line's height at the opening's centre, capped by the
1035
- * line's own ink at that column so tucked skin never shows above it), and the
1036
- * interior's bottom rides the skin's top, so the band is never covered. The
1037
- * interior's top closes onto the same edge, `S + w'`: the whole column scales
1038
- * by `v` about it, so shut its height is zero (a degenerate triangle draws
1039
- * nothing, at every column and between knots; an inverted sliver would leak
1040
- * through texture filtering where the line is thin), and its top sits
1041
- * `w'(1 - v)` above the line's bottom edge, under the line's ink, so the slit
1042
- * between the line and the skin is always backed. The slit opens once
1043
- * `v * H > w'(1 - v)`; that dead zone is accepted: lip-sync noise near 0 does
1044
- * not flicker through it.
1041
+ * The line is thinned at every key: its column is squashed toward its bottom
1042
+ * edge by `UPPER_THIN` (`Tu` in the opening, the line's own bottom past it),
1043
+ * one scale per column so the mesh, linear between its rows, keeps that edge
1044
+ * where it was drawn. The thin belongs to the stack above the line's bottom,
1045
+ * not to the line alone: the contract lets the interior reach up inside the
1046
+ * line's ink (`Tu <= T <= lineTop`), and a line thinned over it would uncover
1047
+ * it under its old top, so the interior's rows above `Tu` come down by the
1048
+ * same scale. The fold below acts on the thinned positions.
1045
1049
  *
1046
- * The seam is the line's drawn bottom edge `Tu` plus one smooth closing
1047
- * travel `D`, a cubic in the column index pinned to zero at the opening's two
1048
- * end columns, so the closed line's ends are the drawn line's own ends. It is
1049
- * fitted by least squares to the raw per-column travel `(T - UPPER_SHARE * H)
1050
- * - Tu`, how far the line's bottom is from the share's point in the
1051
- * interior's span. The travel is fitted, not the seam, because `Tu` is an
1052
- * integer-row read: a smooth seam minus that staircase is a sawtooth the mesh
1053
- * would carry into the rendered line, while a smooth travel carries the
1054
- * drawn stroke (whose antialiased edge is smooth) as one piece. The share sets
1055
- * the travel's depth (the sag), the art's two arcs its shape; the cubic keeps
1056
- * an asymmetric mouth's tilt and cannot wobble.
1050
+ * MouthOpen 0 folds shut onto a seam, 1 is the art as drawn and thinned (the
1051
+ * widening of `mouthWiden` apart). The line comes down until its bottom edge
1052
+ * is on the seam, the skin until its top is one overlap `w'` up under the
1053
+ * line, and the interior's bottom rides the skin's top, so the band is never
1054
+ * covered. The interior's top closes onto the same edge, `S + w'`: the whole
1055
+ * column scales by `v` about it, so shut its height is zero (a degenerate
1056
+ * triangle draws nothing, at every column and between knots; an inverted
1057
+ * sliver would leak through texture filtering where the line is thin), and
1058
+ * its top sits `w'(1 - v)` above the line's bottom edge, under the line's ink,
1059
+ * so the slit between the line and the skin is always backed. The slit opens
1060
+ * once `v * H > w'(1 - v)`; that dead zone is accepted: lip-sync noise near 0
1061
+ * does not flicker through it.
1057
1062
  *
1058
- * A lip's columns outside the opening (the corner hooks) do not fold; only
1059
- * the interior's edge columns read the nearest opening column.
1063
+ * Shut, the line is shaped the way an artist draws a closed mouth: from
1064
+ * `END_TAPER_FROM` of the opening's half-width out, each column is squashed
1065
+ * about its thinned centre row, down to `END_TAPER` of its height at the
1066
+ * line's box edge, so the ends taper; the drawn flick past a corner is pulled
1067
+ * toward it (`FLICK_PULL`) and squashed to `FLICK_SQUASH`. Every closed-key
1068
+ * shape is linear in `1 - v`. `w'` therefore follows the shaped line, not the
1069
+ * drawn one: it is measured up from the closed line's bottom edge (which the
1070
+ * taper raises by half the height it takes off) by the stroke `w` (the line's
1071
+ * height at the opening's centre), capped by the column's own ink and thinned
1072
+ * and squashed with the line. The skin's top and the interior's closing point
1073
+ * then stay inside the line's closed ink at every column, the tapered ends
1074
+ * included, and tucked skin never shows above the line.
1060
1075
  *
1061
- * Knots: the three parts share column knots every `MOUTH_KNOT_PX` plus the
1076
+ * Shut, the line is a smooth parabola across the opening. Its chord joins the
1077
+ * line's smoothed bottom (below) at the two end columns; its depth below the
1078
+ * chord, half way, is the sag at the centre column of the drawn arc plus a
1079
+ * fitted closing travel: a cubic in the column index, pinned to zero at the
1080
+ * end columns and fitted by least squares to the raw per-column travel
1081
+ * `(T - UPPER_SHARE * H) - Tu`, how far the line's bottom is from the share's
1082
+ * point in the interior's span. That sag is capped at `CLOSED_SAG_MAX` of the
1083
+ * opening's width, the drawn arc included, so a deep opening closes into a
1084
+ * gentle smile and a line drawn deeper than the cap closes flatter than
1085
+ * drawn. The seam is `Tu` plus the column's travel: the parabola minus the
1086
+ * line's bottom averaged over 3 columns each side (the smoothed bottom), not
1087
+ * minus `Tu` itself, so the end columns do not travel. `Tu` is an integer-row read, and a curve minus that staircase
1088
+ * is a sawtooth the mesh would carry into the rendered line; against the
1089
+ * smoothed bottom the stroke (whose antialiased edge is smooth) moves as one
1090
+ * piece, and a kink drawn in it (a V at the centre) keeps only its offset
1091
+ * from that average, which the cap counts in. Every closed shape and the
1092
+ * preview are read off the seam.
1093
+ *
1094
+ * The lower lip's columns outside the opening (the corner hooks) do not fold,
1095
+ * the line's are only thinned and shaped (no travel), and the interior's edge
1096
+ * columns read the nearest opening column.
1097
+ *
1098
+ * The inside parts move rigidly per column, after the stack's thin: the teeth
1099
+ * by the line's travel, so they ride under its bottom edge (no taper: the
1100
+ * closed interior clips them away), the tongue by the interior's bottom-edge
1101
+ * travel, so it rides the opening's bottom.
1102
+ *
1103
+ * Knots: the parts share column knots every `MOUTH_KNOT_PX` plus the
1062
1104
  * opening's boundaries `x0`, `x1` and `x1 + 1`, each an integer canvas
1063
- * boundary. The fit's x is the column index, which is the mesh's boundary
1105
+ * boundary. The travel's x is the column index, which is the mesh's boundary
1064
1106
  * position: a knot at boundary `b` samples column `b`, so the mesh renders the
1065
- * fit exactly at its knots and the pinned ends render as pinned (`x1` and
1107
+ * travel exactly at its knots and the pinned ends render as pinned (`x1` and
1066
1108
  * `x1 + 1` both sample the last column; the second sits at its right edge).
1067
- * The preview shifts each column by the field at the column's centre
1109
+ * The preview resamples each column through the lip meshes' own landed rows
1068
1110
  * (`mouthRestShift`). A part's mesh rounds its local x, so one knot
1069
1111
  * reconstructs to slightly different model x per part; the fold therefore
1070
1112
  * reads the knot itself, by vertex index, and maps every knot through one rule
@@ -1093,10 +1135,11 @@ interface OpeningColumn {
1093
1135
  Bl: number;
1094
1136
  /** The interior's span. */
1095
1137
  H: number;
1096
- /** How far the skin tucks up under the line. */
1138
+ /** How far the skin tucks up under the line shut: inside the thinned,
1139
+ * squashed line's ink. */
1097
1140
  overlap: number;
1098
- /** Where the lips meet when shut: the line's bottom edge after its smooth
1099
- * closing travel. */
1141
+ /** Where the lips meet when shut: the line's bottom edge after its closing
1142
+ * travel. */
1100
1143
  seam: number;
1101
1144
  }
1102
1145
  interface Opening {
@@ -1110,23 +1153,44 @@ interface Opening {
1110
1153
  w: number;
1111
1154
  /** A column's values; outside `x0..x1`, or in a gap, the nearest one's. */
1112
1155
  at(col: number): OpeningColumn;
1156
+ /** The upper line at a column of `lip_upper`, model y: inside the opening
1157
+ * the run the contract reads (`Tu`, `lineTop`) and the closed taper's
1158
+ * squash; past it (the drawn flick) the column's own ink span and
1159
+ * `FLICK_SQUASH`; `undefined` where `lip_upper` has no run. */
1160
+ line(col: number): {
1161
+ bottom: number;
1162
+ top: number;
1163
+ squash: number;
1164
+ } | undefined;
1113
1165
  }
1114
1166
  declare function mouthOpening(byRole: Map<string, LayerInput>): Opening;
1115
1167
  /**
1116
- * dy of a model-y rest position at pixel column `col` for MouthOpen `v`
1117
- * (0 shut, 1 as drawn). A lip outside the opening moves 0; `mouth_inner`
1168
+ * `[dx, dy]` of a model-y rest position at pixel column `col` for MouthOpen
1169
+ * `v` (0 shut, 1 as drawn), the thin first at every `v` and the fold on the
1170
+ * thinned position. `lip_upper`'s column is squashed toward its line's bottom;
1171
+ * shut, squashed by its `squash` about the thinned centre row and carried by
1172
+ * the travel, or past the opening (the drawn flick, which now moves too)
1173
+ * squashed and pulled toward the corner. `lip_lower` moves by its travel in
1174
+ * the opening and 0 outside it. `mouth_inner`, its rows above `Tu` thinned,
1118
1175
  * scales its column by `v` about `S + w'`, which carries its top and bottom
1119
1176
  * edges along the line and the skin and needs no read of its own height.
1177
+ * `mouth_teeth`, its rows above `Tu` thinned, moves by the line's travel and
1178
+ * `mouth_tongue` by the interior's bottom edge's, each one number per column.
1120
1179
  */
1121
- declare function mouthFold(role: LipRole, opening: Opening): (col: number, y: number, v: number) => number;
1122
- /** A lip's translate at MouthOpen 0 per canvas column as the mesh renders it,
1123
- * for the composer's closed preview: the linear interpolation at the column's
1124
- * centre between the STORED offsets of the part's two bracketing knots (the
1125
- * numbers the `.iki` carries, rounded as `localWarp` rounds them), 0 outside
1126
- * the part's box. `byRole` must be the layers as the rig measures them (the
1127
- * canvas-sized ones): a box measured on a smaller frame clamps differently,
1128
- * and the knot grid starts at the union's edge. */
1129
- declare function mouthRestShift(byRole: Map<string, LayerInput>): (role: "lip_upper" | "lip_lower", col: number) => number;
1180
+ declare function mouthFold(role: LipRole | LipInsideRole, opening: Opening): (col: number, y: number, v: number) => [number, number];
1181
+ /** A lip's dy at MouthOpen 0 at a canvas pixel `(col, row)` as the mesh
1182
+ * renders it, for the composer's closed preview: read at the pixel's centre
1183
+ * off the STORED offsets (the numbers the `.iki` carries, rounded as
1184
+ * `localWarp` rounds them) of the `columnMesh` triangle containing it — the
1185
+ * cell by knot and row, its triangle by the cell's top-left–bottom-right
1186
+ * diagonal, the three vertices weighted barycentrically. The thin and the
1187
+ * taper move a column's rows by different amounts and the renderer is
1188
+ * linear over a triangle, never bilinear over a cell, so no one row stands
1189
+ * for the column. `undefined` for a pixel centre outside the part's box (the
1190
+ * mesh's domain), which has no field. `byRole` must be the layers as the rig
1191
+ * measures them (the canvas-sized ones): a box measured on a smaller frame
1192
+ * clamps differently, and the knot grid starts at the union's edge. */
1193
+ declare function mouthRestShift(byRole: Map<string, LayerInput>): (role: "lip_upper" | "lip_lower", col: number, row: number) => number | undefined;
1130
1194
 
1131
1195
  /**
1132
1196
  * `generateIkiFromLayerSet`: role layers in, a rigged `.iki` out.
@@ -1147,7 +1211,8 @@ declare function mouthRestShift(byRole: Map<string, LayerInput>): (role: "lip_up
1147
1211
  * the default expressions and the Nod, Shake and Tilt motions, and
1148
1212
  * with a pose forearm the Wave (`animations.ts`). The mouth has two paths,
1149
1213
  * chosen by the layers: `mouth` alone stretches, with `mouth_open` it
1150
- * crossfades; the lip set folds open like the eyelid (`mouth.ts`).
1214
+ * crossfades; the lip set folds open like the eyelid (`mouth.ts`), its
1215
+ * optional `mouth_tongue` and `mouth_teeth` clipped to its interior.
1151
1216
  */
1152
1217
 
1153
1218
  declare function generateIkiFromLayerSet(layers: LayerInput[], canvas: {
@@ -1451,10 +1516,17 @@ interface AtlasLayout {
1451
1516
  * BOTTOM only. Page left/top edges need no gutter.
1452
1517
  *
1453
1518
  * pageWidth/pageHeight = tight bound (max x+width+padding, max y+height+padding).
1519
+ * When `maxSide` is given and the first layout is taller than it but not wider,
1520
+ * the pack is retried at every width from the widest padded source up to `maxSide`, and the first
1521
+ * layout that fits the height wins (shelf height is not monotonic in width);
1522
+ * when none does the first is returned for the caller to refuse. The attempts
1523
+ * are every width for this shelf order, not a proof that no packing exists.
1524
+ *
1454
1525
  * Empty sources → { pageWidth: 0, pageHeight: 0, placements: [], padding }.
1455
- * Throws a plain Error naming the offending source id on a non-finite or <= 0 dimension.
1526
+ * Throws a plain Error naming the offending source id on a non-finite or <= 0 dimension,
1527
+ * and on a supplied `maxSide` that is non-finite or <= 0.
1456
1528
  */
1457
- declare function packAtlas(sources: AtlasSource[], padding?: number): AtlasLayout;
1529
+ declare function packAtlas(sources: AtlasSource[], padding?: number, maxSide?: number): AtlasLayout;
1458
1530
  /**
1459
1531
  * Convert a pixel placement into a UV rect, inset by `insetPx` on all four
1460
1532
  * edges and clamped to [0, 1] so the validator's bounds check always passes.
@@ -1545,4 +1617,4 @@ declare function createGridMesh(cols: number, rows: number): IkiMesh;
1545
1617
  */
1546
1618
  declare function createDefaultWarpDeformer(model: IkiModel): IkiWarpDeformer;
1547
1619
 
1548
- export { ALPHA_BBOX_THRESHOLD, ALPHA_OPAQUE, ATLAS_PADDING, AddDeformer, AddPart, AddPhysicsRig, type AlphaBbox, type ApplyAtlasInput, type ArmGeometry, type AtlasAssignment, type AtlasLayout, type AtlasPlacement, type AtlasSource, CaptureGridKeyform, DEFAULT_TURN_TARGETS, type DeformerTransformChannel, DeleteDeformer, DeletePart, DeletePhysicsRig, type EditCommand, type EditTransformChannel, EditorDocument, type ForearmPoseGeometry, HEAD_BAND, type IrisStrand, LIP_ROLES, LayerGeometryError, type LayerInput, type LayerSetMeasurement, type LayerSetMeasurer, type LipRole, type Opening, type OpeningColumn, type RgbaLayer, type RigStyle, SPECK_CORE_FRACTION, SetDeformerBindings, SetDeformerParent, SetDeformerPivot, SetDeformerPivotX, SetDeformerPivotY, SetDeformerTransform, SetPartBindings, SetPartColor, SetPartDeformer, SetPartHeight, SetPartMesh, SetPartOrder, SetPartTransform, SetPartWidth, SetPhysicsRig, type StrandOverlap, type TurnDepths, type TurnSolveReport, TurnTargetError, type TurnTargets, UV_INSET_PX, armGeometry, captureBindingEndpoint, columnRuns, computeGridOffsets, createDefaultMatrixDeformer, createDefaultPart, createDefaultWarpDeformer, createGridMesh, createLayerSetMeasurer, denseCoreOf, detectAlphaBbox, forearmPoseGeometry, foregroundSpan, generateIkiFromLayerSet, headHalfOf, interpolateGridOffsets, isSpeckCore, mouthFold, mouthOpening, mouthRestShift, packAtlas, parseLayerRoles, partIdsOfRole, upsertGridKeyform, uvRectFor, validateDeformerDelete, validateDeformerReparent, validatePartAttach };
1620
+ export { ALPHA_BBOX_THRESHOLD, ALPHA_OPAQUE, ATLAS_PADDING, AddDeformer, AddPart, AddPhysicsRig, type AlphaBbox, type ApplyAtlasInput, type ArmGeometry, type AtlasAssignment, type AtlasLayout, type AtlasPlacement, type AtlasSource, CaptureGridKeyform, DEFAULT_TURN_TARGETS, type DeformerTransformChannel, DeleteDeformer, DeletePart, DeletePhysicsRig, type EditCommand, type EditTransformChannel, EditorDocument, type ForearmPoseGeometry, HEAD_BAND, type IrisStrand, LIP_INSIDE_ROLES, LIP_ROLES, LayerGeometryError, type LayerInput, type LayerSetMeasurement, type LayerSetMeasurer, type LipInsideRole, type LipRole, type Opening, type OpeningColumn, type RgbaLayer, type RigStyle, SPECK_CORE_FRACTION, SetDeformerBindings, SetDeformerParent, SetDeformerPivot, SetDeformerPivotX, SetDeformerPivotY, SetDeformerTransform, SetPartBindings, SetPartColor, SetPartDeformer, SetPartHeight, SetPartMesh, SetPartOrder, SetPartTransform, SetPartWidth, SetPhysicsRig, type StrandOverlap, type TurnDepths, type TurnSolveReport, TurnTargetError, type TurnTargets, UV_INSET_PX, armGeometry, captureBindingEndpoint, columnRuns, computeGridOffsets, createDefaultMatrixDeformer, createDefaultPart, createDefaultWarpDeformer, createGridMesh, createLayerSetMeasurer, denseCoreOf, detectAlphaBbox, forearmPoseGeometry, foregroundSpan, generateIkiFromLayerSet, headHalfOf, interpolateGridOffsets, isLipInsideRole, isSpeckCore, mouthFold, mouthOpening, mouthRestShift, packAtlas, parseLayerRoles, partIdsOfRole, upsertGridKeyform, uvRectFor, validateDeformerDelete, validateDeformerReparent, validatePartAttach };