@ikijs/editor 0.21.0 → 0.23.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
@@ -40,24 +40,24 @@ leaves the document untouched.
40
40
 
41
41
  ## API
42
42
 
43
- | Area | Exports |
44
- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
45
- | Document | `EditorDocument`, `EditCommand` |
46
- | Part edits | `AddPart`, `DeletePart`, `SetPartColor`, `SetPartWidth`, `SetPartHeight`, `SetPartOrder`, `SetPartTransform`, `SetPartBindings`, `SetPartMesh`, `SetPartDeformer` |
47
- | Deformer edits | `AddDeformer`, `DeleteDeformer`, `SetDeformerParent`, `SetDeformerTransform`, `SetDeformerBindings`, `SetDeformerPivot` (+ `X`/`Y`), `CaptureGridKeyform` |
48
- | Physics edits | `AddPhysicsRig`, `SetPhysicsRig`, `DeletePhysicsRig` |
49
- | Referential guards | `validateDeformerReparent`, `validateDeformerDelete`, `validatePartAttach` |
50
- | Atlas | `packAtlas`, `uvRectFor`, `ATLAS_PADDING`, `UV_INSET_PX` |
51
- | Grid keyforms | `computeGridOffsets`, `interpolateGridOffsets`, `upsertGridKeyform` |
52
- | Factories | `createDefaultPart`, `createDefaultMatrixDeformer`, `createDefaultWarpDeformer`, `createGridMesh` |
53
- | Pixels | `detectAlphaBbox`, `ALPHA_BBOX_THRESHOLD`, `AlphaBbox`, `ALPHA_OPAQUE`, `HEAD_BAND`, `SPECK_CORE_FRACTION`, `denseCoreOf`, `isSpeckCore`, `foregroundSpan`, `headHalfOf` |
54
- | Auto-rig | `generateIkiFromLayerSet`, `parseLayerRoles`, `partIdsOfRole`, `createLayerSetMeasurer`, `armGeometry`, `ArmGeometry`, `forearmPoseGeometry`, `ForearmPoseGeometry`, `TurnTargets`, `DEFAULT_TURN_TARGETS`, `TurnSolveReport`, `RigStyle`, `LayerGeometryError` |
55
- | Bindings | `captureBindingEndpoint` |
43
+ | Area | Exports |
44
+ | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
45
+ | Document | `EditorDocument`, `EditCommand` |
46
+ | Part edits | `AddPart`, `DeletePart`, `SetPartColor`, `SetPartWidth`, `SetPartHeight`, `SetPartOrder`, `SetPartTransform`, `SetPartBindings`, `SetPartMesh`, `SetPartDeformer` |
47
+ | Deformer edits | `AddDeformer`, `DeleteDeformer`, `SetDeformerParent`, `SetDeformerTransform`, `SetDeformerBindings`, `SetDeformerPivot` (+ `X`/`Y`), `CaptureGridKeyform` |
48
+ | Physics edits | `AddPhysicsRig`, `SetPhysicsRig`, `DeletePhysicsRig` |
49
+ | Referential guards | `validateDeformerReparent`, `validateDeformerDelete`, `validatePartAttach` |
50
+ | Atlas | `packAtlas`, `uvRectFor`, `ATLAS_PADDING`, `UV_INSET_PX` |
51
+ | Grid keyforms | `computeGridOffsets`, `interpolateGridOffsets`, `upsertGridKeyform` |
52
+ | Factories | `createDefaultPart`, `createDefaultMatrixDeformer`, `createDefaultWarpDeformer`, `createGridMesh` |
53
+ | Pixels | `detectAlphaBbox`, `ALPHA_BBOX_THRESHOLD`, `AlphaBbox`, `ALPHA_OPAQUE`, `HEAD_BAND`, `SPECK_CORE_FRACTION`, `denseCoreOf`, `isSpeckCore`, `foregroundSpan`, `headHalfOf` |
54
+ | Auto-rig | `generateIkiFromLayerSet`, `parseLayerRoles`, `partIdsOfRole`, `createLayerSetMeasurer`, `armGeometry`, `ArmGeometry`, `forearmPoseGeometry`, `ForearmPoseGeometry`, `mouthOpening`, `mouthFold`, `mouthRestShift`, `columnRuns`, `LIP_ROLES`, `LipRole`, `Opening`, `OpeningColumn`, `TurnTargets`, `DEFAULT_TURN_TARGETS`, `TurnSolveReport`, `RigStyle`, `LayerGeometryError` |
55
+ | Bindings | `captureBindingEndpoint` |
56
56
 
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
@@ -993,20 +993,196 @@ interface ForearmPoseGeometry {
993
993
  */
994
994
  declare function forearmPoseGeometry(layer: LayerInput): ForearmPoseGeometry;
995
995
 
996
+ /** The lip set's roles, back to front (`mouth.ts`). */
997
+ declare const LIP_ROLES: readonly ["mouth_inner", "lip_lower", "lip_upper"];
998
+ type LipRole = (typeof LIP_ROLES)[number];
999
+ /** The lip set's optional inside parts, back to front: cut from the interior
1000
+ * and clipped to `mouth_inner` (`mouth.ts`). */
1001
+ declare const LIP_INSIDE_ROLES: readonly ["mouth_tongue", "mouth_teeth"];
1002
+ type LipInsideRole = (typeof LIP_INSIDE_ROLES)[number];
1003
+ declare const isLipInsideRole: (role: string) => role is LipInsideRole;
996
1004
  /** The part ids a `role`'s layer becomes, back to front: every one of them
997
1005
  * takes its texture from the layer's crop. Throws on an unknown role. */
998
1006
  declare function partIdsOfRole(role: string): readonly string[];
999
1007
  /**
1000
1008
  * Map file names to roles. Throws on an unknown role, a role named twice, a
1001
1009
  * required role (`face`, `eye_L`, `eye_R`) missing, no mouth (`mouth`, or the
1002
- * lip set) or a partial or mixed one, an arm without a body, or a pose forearm
1003
- * without its arm.
1010
+ * lip set) or a partial or mixed one, a lip set's inside part without the set,
1011
+ * an arm without a body, or a pose forearm without its arm.
1004
1012
  */
1005
1013
  declare function parseLayerRoles(fileNames: string[]): {
1006
1014
  role: string;
1007
1015
  fileName: string;
1008
1016
  }[];
1009
1017
 
1018
+ /**
1019
+ * The lip set: a mouth that folds open the way the eyelid folds shut. Three
1020
+ * parts, back to front: `mouth_inner` (the interior and the opening's lower
1021
+ * outline band), `lip_lower` (the lower lip's skin) and `lip_upper` (the upper
1022
+ * lip line and the corner hooks). Between the interior and the skin it may
1023
+ * carry two inside parts, `mouth_tongue` then `mouth_teeth`, cut from the
1024
+ * interior and clipped to it: the rig reads nothing off them but their box.
1025
+ *
1026
+ * What the rig reads is the layers' own alpha edges, as exclusive run
1027
+ * boundaries in model y (+y up; a run of crop rows `[a, b)` is the span
1028
+ * `[rowTop(b), rowTop(a)]`). Per canvas column inside the opening (where
1029
+ * `mouth_inner` has a run): the interior's top `T` and bottom `Bb`, the line
1030
+ * of `lip_upper` at the interior's top (`Tu` its bottom edge, `lineTop` its
1031
+ * top) and the skin's top `Bl`. Each part has one affine warp per column that
1032
+ * lands its OWN boundary on its target, so no relation between two parts'
1033
+ * boundaries enters the closure; there is no colour read. The art's contract:
1034
+ * the interior reaches the line (`T >= Tu`; lower would show the face between
1035
+ * them at rest) and the skin reaches the interior (`Bl >= Bb`; an overlap is
1036
+ * fine, a gap is not).
1037
+ *
1038
+ * The line is thinned at every key: its column is squashed toward its bottom
1039
+ * edge by `UPPER_THIN` (`Tu` in the opening, the line's own bottom past it),
1040
+ * one scale per column so the mesh, linear between its rows, keeps that edge
1041
+ * where it was drawn. The thin belongs to the stack above the line's bottom,
1042
+ * not to the line alone: the contract lets the interior reach up inside the
1043
+ * line's ink (`Tu <= T <= lineTop`), and a line thinned over it would uncover
1044
+ * it under its old top, so the interior's rows above `Tu` come down by the
1045
+ * same scale. The fold below acts on the thinned positions.
1046
+ *
1047
+ * MouthOpen 0 folds shut onto a seam, 1 is the art as drawn and thinned (the
1048
+ * widening of `mouthWiden` apart). The line comes down until its bottom edge
1049
+ * is on the seam, the skin until its top is one overlap `w'` up under the
1050
+ * line, and the interior's bottom rides the skin's top, so the band is never
1051
+ * covered. The interior's top closes onto the same edge, `S + w'`: the whole
1052
+ * column scales by `v` about it, so shut its height is zero (a degenerate
1053
+ * triangle draws nothing, at every column and between knots; an inverted
1054
+ * sliver would leak through texture filtering where the line is thin), and
1055
+ * its top sits `w'(1 - v)` above the line's bottom edge, under the line's ink,
1056
+ * so the slit between the line and the skin is always backed. The slit opens
1057
+ * once `v * H > w'(1 - v)`; that dead zone is accepted: lip-sync noise near 0
1058
+ * does not flicker through it.
1059
+ *
1060
+ * Shut, the line is shaped the way an artist draws a closed mouth: from
1061
+ * `END_TAPER_FROM` of the opening's half-width out, each column is squashed
1062
+ * about its thinned centre row, down to `END_TAPER` of its height at the
1063
+ * line's box edge, so the ends taper; the drawn flick past a corner is pulled
1064
+ * toward it (`FLICK_PULL`) and squashed to `FLICK_SQUASH`. Every closed-key
1065
+ * shape is linear in `1 - v`. `w'` therefore follows the shaped line, not the
1066
+ * drawn one: it is measured up from the closed line's bottom edge (which the
1067
+ * taper raises by half the height it takes off) by the stroke `w` (the line's
1068
+ * height at the opening's centre), capped by the column's own ink and thinned
1069
+ * and squashed with the line. The skin's top and the interior's closing point
1070
+ * then stay inside the line's closed ink at every column, the tapered ends
1071
+ * included, and tucked skin never shows above the line.
1072
+ *
1073
+ * The seam is the line's drawn bottom edge `Tu` plus one smooth closing
1074
+ * travel `D`, a cubic in the column index pinned to zero at the opening's two
1075
+ * end columns, so the closed line's ends are the drawn line's own ends. It is
1076
+ * fitted by least squares to the raw per-column travel `(T - UPPER_SHARE * H)
1077
+ * - Tu`, how far the line's bottom is from the share's point in the
1078
+ * interior's span. The travel is fitted, not the seam, because `Tu` is an
1079
+ * integer-row read: a smooth seam minus that staircase is a sawtooth the mesh
1080
+ * would carry into the rendered line, while a smooth travel carries the
1081
+ * drawn stroke (whose antialiased edge is smooth) as one piece. The share sets
1082
+ * the travel's depth (the sag), the art's two arcs its shape; the cubic keeps
1083
+ * an asymmetric mouth's tilt and cannot wobble.
1084
+ *
1085
+ * The lower lip's columns outside the opening (the corner hooks) do not fold,
1086
+ * the line's are only thinned and shaped (no travel), and the interior's edge
1087
+ * columns read the nearest opening column.
1088
+ *
1089
+ * The inside parts move rigidly per column, after the stack's thin: the teeth
1090
+ * by the line's travel, so they ride under its bottom edge (no taper: the
1091
+ * closed interior clips them away), the tongue by the interior's bottom-edge
1092
+ * travel, so it rides the opening's bottom.
1093
+ *
1094
+ * Knots: the parts share column knots every `MOUTH_KNOT_PX` plus the
1095
+ * opening's boundaries `x0`, `x1` and `x1 + 1`, each an integer canvas
1096
+ * boundary. The fit's x is the column index, which is the mesh's boundary
1097
+ * position: a knot at boundary `b` samples column `b`, so the mesh renders the
1098
+ * fit exactly at its knots and the pinned ends render as pinned (`x1` and
1099
+ * `x1 + 1` both sample the last column; the second sits at its right edge).
1100
+ * The preview resamples each column through the lip meshes' own landed rows
1101
+ * (`mouthRestShift`). A part's mesh rounds its local x, so one knot
1102
+ * reconstructs to slightly different model x per part; the fold therefore
1103
+ * reads the knot itself, by vertex index, and maps every knot through one rule
1104
+ * (`columnOfKnot`).
1105
+ *
1106
+ * The anchor (`mouthAnchor`) is where the head's frame and the turn read the
1107
+ * mouth's resting place (the chin lead, the chin estimate, the solve). A
1108
+ * single closed mouth is its box centre (its box the union with `mouth_open`);
1109
+ * for a lip set it is the seam at the opening's centre column, where the lips
1110
+ * meet, with the interior's centre across and the union of the three as box.
1111
+ */
1112
+
1113
+ /** Per canvas column, its opaque runs as `[top, bottom)` crop rows, top
1114
+ * first: the transpose of `rowRuns`. */
1115
+ declare function columnRuns(layer: LayerInput): Map<number, number[][]>;
1116
+ /** What the fold reads off one canvas column, all model y. */
1117
+ interface OpeningColumn {
1118
+ /** The interior's top and bottom edges. */
1119
+ T: number;
1120
+ Bb: number;
1121
+ /** The upper line's bottom and top edges, and its height. */
1122
+ Tu: number;
1123
+ lineTop: number;
1124
+ lineH: number;
1125
+ /** The skin's top edge. */
1126
+ Bl: number;
1127
+ /** The interior's span. */
1128
+ H: number;
1129
+ /** How far the skin tucks up under the line shut: inside the thinned,
1130
+ * squashed line's ink. */
1131
+ overlap: number;
1132
+ /** Where the lips meet when shut: the line's bottom edge after its smooth
1133
+ * closing travel. */
1134
+ seam: number;
1135
+ }
1136
+ interface Opening {
1137
+ /** The first and last canvas columns where `mouth_inner` has a run. */
1138
+ x0: number;
1139
+ x1: number;
1140
+ /** The opening's centre column: where the stroke and the anchor are read. */
1141
+ centre: number;
1142
+ canvasW: number;
1143
+ /** The stroke: the line's height at the opening's centre column. */
1144
+ w: number;
1145
+ /** A column's values; outside `x0..x1`, or in a gap, the nearest one's. */
1146
+ at(col: number): OpeningColumn;
1147
+ /** The upper line at a column of `lip_upper`, model y: inside the opening
1148
+ * the run the contract reads (`Tu`, `lineTop`) and the closed taper's
1149
+ * squash; past it (the drawn flick) the column's own ink span and
1150
+ * `FLICK_SQUASH`; `undefined` where `lip_upper` has no run. */
1151
+ line(col: number): {
1152
+ bottom: number;
1153
+ top: number;
1154
+ squash: number;
1155
+ } | undefined;
1156
+ }
1157
+ declare function mouthOpening(byRole: Map<string, LayerInput>): Opening;
1158
+ /**
1159
+ * `[dx, dy]` of a model-y rest position at pixel column `col` for MouthOpen
1160
+ * `v` (0 shut, 1 as drawn), the thin first at every `v` and the fold on the
1161
+ * thinned position. `lip_upper`'s column is squashed toward its line's bottom;
1162
+ * shut, squashed by its `squash` about the thinned centre row and carried by
1163
+ * the travel, or past the opening (the drawn flick, which now moves too)
1164
+ * squashed and pulled toward the corner. `lip_lower` moves by its travel in
1165
+ * the opening and 0 outside it. `mouth_inner`, its rows above `Tu` thinned,
1166
+ * scales its column by `v` about `S + w'`, which carries its top and bottom
1167
+ * edges along the line and the skin and needs no read of its own height.
1168
+ * `mouth_teeth`, its rows above `Tu` thinned, moves by the line's travel and
1169
+ * `mouth_tongue` by the interior's bottom edge's, each one number per column.
1170
+ */
1171
+ declare function mouthFold(role: LipRole | LipInsideRole, opening: Opening): (col: number, y: number, v: number) => [number, number];
1172
+ /** A lip's dy at MouthOpen 0 at a canvas pixel `(col, row)` as the mesh
1173
+ * renders it, for the composer's closed preview: read at the pixel's centre
1174
+ * off the STORED offsets (the numbers the `.iki` carries, rounded as
1175
+ * `localWarp` rounds them) of the `columnMesh` triangle containing it — the
1176
+ * cell by knot and row, its triangle by the cell's top-left–bottom-right
1177
+ * diagonal, the three vertices weighted barycentrically. The thin and the
1178
+ * taper move a column's rows by different amounts and the renderer is
1179
+ * linear over a triangle, never bilinear over a cell, so no one row stands
1180
+ * for the column. `undefined` for a pixel centre outside the part's box (the
1181
+ * mesh's domain), which has no field. `byRole` must be the layers as the rig
1182
+ * measures them (the canvas-sized ones): a box measured on a smaller frame
1183
+ * clamps differently, and the knot grid starts at the union's edge. */
1184
+ declare function mouthRestShift(byRole: Map<string, LayerInput>): (role: "lip_upper" | "lip_lower", col: number, row: number) => number | undefined;
1185
+
1010
1186
  /**
1011
1187
  * `generateIkiFromLayerSet`: role layers in, a rigged `.iki` out.
1012
1188
  *
@@ -1026,7 +1202,8 @@ declare function parseLayerRoles(fileNames: string[]): {
1026
1202
  * the default expressions and the Nod, Shake and Tilt motions, and
1027
1203
  * with a pose forearm the Wave (`animations.ts`). The mouth has two paths,
1028
1204
  * chosen by the layers: `mouth` alone stretches, with `mouth_open` it
1029
- * crossfades; the lip set folds open like the eyelid (`mouth.ts`).
1205
+ * crossfades; the lip set folds open like the eyelid (`mouth.ts`), its
1206
+ * optional `mouth_tongue` and `mouth_teeth` clipped to its interior.
1030
1207
  */
1031
1208
 
1032
1209
  declare function generateIkiFromLayerSet(layers: LayerInput[], canvas: {
@@ -1424,4 +1601,4 @@ declare function createGridMesh(cols: number, rows: number): IkiMesh;
1424
1601
  */
1425
1602
  declare function createDefaultWarpDeformer(model: IkiModel): IkiWarpDeformer;
1426
1603
 
1427
- 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, LayerGeometryError, type LayerInput, type LayerSetMeasurement, type LayerSetMeasurer, 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, computeGridOffsets, createDefaultMatrixDeformer, createDefaultPart, createDefaultWarpDeformer, createGridMesh, createLayerSetMeasurer, denseCoreOf, detectAlphaBbox, forearmPoseGeometry, foregroundSpan, generateIkiFromLayerSet, headHalfOf, interpolateGridOffsets, isSpeckCore, packAtlas, parseLayerRoles, partIdsOfRole, upsertGridKeyform, uvRectFor, validateDeformerDelete, validateDeformerReparent, validatePartAttach };
1604
+ 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
@@ -993,20 +993,196 @@ interface ForearmPoseGeometry {
993
993
  */
994
994
  declare function forearmPoseGeometry(layer: LayerInput): ForearmPoseGeometry;
995
995
 
996
+ /** The lip set's roles, back to front (`mouth.ts`). */
997
+ declare const LIP_ROLES: readonly ["mouth_inner", "lip_lower", "lip_upper"];
998
+ type LipRole = (typeof LIP_ROLES)[number];
999
+ /** The lip set's optional inside parts, back to front: cut from the interior
1000
+ * and clipped to `mouth_inner` (`mouth.ts`). */
1001
+ declare const LIP_INSIDE_ROLES: readonly ["mouth_tongue", "mouth_teeth"];
1002
+ type LipInsideRole = (typeof LIP_INSIDE_ROLES)[number];
1003
+ declare const isLipInsideRole: (role: string) => role is LipInsideRole;
996
1004
  /** The part ids a `role`'s layer becomes, back to front: every one of them
997
1005
  * takes its texture from the layer's crop. Throws on an unknown role. */
998
1006
  declare function partIdsOfRole(role: string): readonly string[];
999
1007
  /**
1000
1008
  * Map file names to roles. Throws on an unknown role, a role named twice, a
1001
1009
  * required role (`face`, `eye_L`, `eye_R`) missing, no mouth (`mouth`, or the
1002
- * lip set) or a partial or mixed one, an arm without a body, or a pose forearm
1003
- * without its arm.
1010
+ * lip set) or a partial or mixed one, a lip set's inside part without the set,
1011
+ * an arm without a body, or a pose forearm without its arm.
1004
1012
  */
1005
1013
  declare function parseLayerRoles(fileNames: string[]): {
1006
1014
  role: string;
1007
1015
  fileName: string;
1008
1016
  }[];
1009
1017
 
1018
+ /**
1019
+ * The lip set: a mouth that folds open the way the eyelid folds shut. Three
1020
+ * parts, back to front: `mouth_inner` (the interior and the opening's lower
1021
+ * outline band), `lip_lower` (the lower lip's skin) and `lip_upper` (the upper
1022
+ * lip line and the corner hooks). Between the interior and the skin it may
1023
+ * carry two inside parts, `mouth_tongue` then `mouth_teeth`, cut from the
1024
+ * interior and clipped to it: the rig reads nothing off them but their box.
1025
+ *
1026
+ * What the rig reads is the layers' own alpha edges, as exclusive run
1027
+ * boundaries in model y (+y up; a run of crop rows `[a, b)` is the span
1028
+ * `[rowTop(b), rowTop(a)]`). Per canvas column inside the opening (where
1029
+ * `mouth_inner` has a run): the interior's top `T` and bottom `Bb`, the line
1030
+ * of `lip_upper` at the interior's top (`Tu` its bottom edge, `lineTop` its
1031
+ * top) and the skin's top `Bl`. Each part has one affine warp per column that
1032
+ * lands its OWN boundary on its target, so no relation between two parts'
1033
+ * boundaries enters the closure; there is no colour read. The art's contract:
1034
+ * the interior reaches the line (`T >= Tu`; lower would show the face between
1035
+ * them at rest) and the skin reaches the interior (`Bl >= Bb`; an overlap is
1036
+ * fine, a gap is not).
1037
+ *
1038
+ * The line is thinned at every key: its column is squashed toward its bottom
1039
+ * edge by `UPPER_THIN` (`Tu` in the opening, the line's own bottom past it),
1040
+ * one scale per column so the mesh, linear between its rows, keeps that edge
1041
+ * where it was drawn. The thin belongs to the stack above the line's bottom,
1042
+ * not to the line alone: the contract lets the interior reach up inside the
1043
+ * line's ink (`Tu <= T <= lineTop`), and a line thinned over it would uncover
1044
+ * it under its old top, so the interior's rows above `Tu` come down by the
1045
+ * same scale. The fold below acts on the thinned positions.
1046
+ *
1047
+ * MouthOpen 0 folds shut onto a seam, 1 is the art as drawn and thinned (the
1048
+ * widening of `mouthWiden` apart). The line comes down until its bottom edge
1049
+ * is on the seam, the skin until its top is one overlap `w'` up under the
1050
+ * line, and the interior's bottom rides the skin's top, so the band is never
1051
+ * covered. The interior's top closes onto the same edge, `S + w'`: the whole
1052
+ * column scales by `v` about it, so shut its height is zero (a degenerate
1053
+ * triangle draws nothing, at every column and between knots; an inverted
1054
+ * sliver would leak through texture filtering where the line is thin), and
1055
+ * its top sits `w'(1 - v)` above the line's bottom edge, under the line's ink,
1056
+ * so the slit between the line and the skin is always backed. The slit opens
1057
+ * once `v * H > w'(1 - v)`; that dead zone is accepted: lip-sync noise near 0
1058
+ * does not flicker through it.
1059
+ *
1060
+ * Shut, the line is shaped the way an artist draws a closed mouth: from
1061
+ * `END_TAPER_FROM` of the opening's half-width out, each column is squashed
1062
+ * about its thinned centre row, down to `END_TAPER` of its height at the
1063
+ * line's box edge, so the ends taper; the drawn flick past a corner is pulled
1064
+ * toward it (`FLICK_PULL`) and squashed to `FLICK_SQUASH`. Every closed-key
1065
+ * shape is linear in `1 - v`. `w'` therefore follows the shaped line, not the
1066
+ * drawn one: it is measured up from the closed line's bottom edge (which the
1067
+ * taper raises by half the height it takes off) by the stroke `w` (the line's
1068
+ * height at the opening's centre), capped by the column's own ink and thinned
1069
+ * and squashed with the line. The skin's top and the interior's closing point
1070
+ * then stay inside the line's closed ink at every column, the tapered ends
1071
+ * included, and tucked skin never shows above the line.
1072
+ *
1073
+ * The seam is the line's drawn bottom edge `Tu` plus one smooth closing
1074
+ * travel `D`, a cubic in the column index pinned to zero at the opening's two
1075
+ * end columns, so the closed line's ends are the drawn line's own ends. It is
1076
+ * fitted by least squares to the raw per-column travel `(T - UPPER_SHARE * H)
1077
+ * - Tu`, how far the line's bottom is from the share's point in the
1078
+ * interior's span. The travel is fitted, not the seam, because `Tu` is an
1079
+ * integer-row read: a smooth seam minus that staircase is a sawtooth the mesh
1080
+ * would carry into the rendered line, while a smooth travel carries the
1081
+ * drawn stroke (whose antialiased edge is smooth) as one piece. The share sets
1082
+ * the travel's depth (the sag), the art's two arcs its shape; the cubic keeps
1083
+ * an asymmetric mouth's tilt and cannot wobble.
1084
+ *
1085
+ * The lower lip's columns outside the opening (the corner hooks) do not fold,
1086
+ * the line's are only thinned and shaped (no travel), and the interior's edge
1087
+ * columns read the nearest opening column.
1088
+ *
1089
+ * The inside parts move rigidly per column, after the stack's thin: the teeth
1090
+ * by the line's travel, so they ride under its bottom edge (no taper: the
1091
+ * closed interior clips them away), the tongue by the interior's bottom-edge
1092
+ * travel, so it rides the opening's bottom.
1093
+ *
1094
+ * Knots: the parts share column knots every `MOUTH_KNOT_PX` plus the
1095
+ * opening's boundaries `x0`, `x1` and `x1 + 1`, each an integer canvas
1096
+ * boundary. The fit's x is the column index, which is the mesh's boundary
1097
+ * position: a knot at boundary `b` samples column `b`, so the mesh renders the
1098
+ * fit exactly at its knots and the pinned ends render as pinned (`x1` and
1099
+ * `x1 + 1` both sample the last column; the second sits at its right edge).
1100
+ * The preview resamples each column through the lip meshes' own landed rows
1101
+ * (`mouthRestShift`). A part's mesh rounds its local x, so one knot
1102
+ * reconstructs to slightly different model x per part; the fold therefore
1103
+ * reads the knot itself, by vertex index, and maps every knot through one rule
1104
+ * (`columnOfKnot`).
1105
+ *
1106
+ * The anchor (`mouthAnchor`) is where the head's frame and the turn read the
1107
+ * mouth's resting place (the chin lead, the chin estimate, the solve). A
1108
+ * single closed mouth is its box centre (its box the union with `mouth_open`);
1109
+ * for a lip set it is the seam at the opening's centre column, where the lips
1110
+ * meet, with the interior's centre across and the union of the three as box.
1111
+ */
1112
+
1113
+ /** Per canvas column, its opaque runs as `[top, bottom)` crop rows, top
1114
+ * first: the transpose of `rowRuns`. */
1115
+ declare function columnRuns(layer: LayerInput): Map<number, number[][]>;
1116
+ /** What the fold reads off one canvas column, all model y. */
1117
+ interface OpeningColumn {
1118
+ /** The interior's top and bottom edges. */
1119
+ T: number;
1120
+ Bb: number;
1121
+ /** The upper line's bottom and top edges, and its height. */
1122
+ Tu: number;
1123
+ lineTop: number;
1124
+ lineH: number;
1125
+ /** The skin's top edge. */
1126
+ Bl: number;
1127
+ /** The interior's span. */
1128
+ H: number;
1129
+ /** How far the skin tucks up under the line shut: inside the thinned,
1130
+ * squashed line's ink. */
1131
+ overlap: number;
1132
+ /** Where the lips meet when shut: the line's bottom edge after its smooth
1133
+ * closing travel. */
1134
+ seam: number;
1135
+ }
1136
+ interface Opening {
1137
+ /** The first and last canvas columns where `mouth_inner` has a run. */
1138
+ x0: number;
1139
+ x1: number;
1140
+ /** The opening's centre column: where the stroke and the anchor are read. */
1141
+ centre: number;
1142
+ canvasW: number;
1143
+ /** The stroke: the line's height at the opening's centre column. */
1144
+ w: number;
1145
+ /** A column's values; outside `x0..x1`, or in a gap, the nearest one's. */
1146
+ at(col: number): OpeningColumn;
1147
+ /** The upper line at a column of `lip_upper`, model y: inside the opening
1148
+ * the run the contract reads (`Tu`, `lineTop`) and the closed taper's
1149
+ * squash; past it (the drawn flick) the column's own ink span and
1150
+ * `FLICK_SQUASH`; `undefined` where `lip_upper` has no run. */
1151
+ line(col: number): {
1152
+ bottom: number;
1153
+ top: number;
1154
+ squash: number;
1155
+ } | undefined;
1156
+ }
1157
+ declare function mouthOpening(byRole: Map<string, LayerInput>): Opening;
1158
+ /**
1159
+ * `[dx, dy]` of a model-y rest position at pixel column `col` for MouthOpen
1160
+ * `v` (0 shut, 1 as drawn), the thin first at every `v` and the fold on the
1161
+ * thinned position. `lip_upper`'s column is squashed toward its line's bottom;
1162
+ * shut, squashed by its `squash` about the thinned centre row and carried by
1163
+ * the travel, or past the opening (the drawn flick, which now moves too)
1164
+ * squashed and pulled toward the corner. `lip_lower` moves by its travel in
1165
+ * the opening and 0 outside it. `mouth_inner`, its rows above `Tu` thinned,
1166
+ * scales its column by `v` about `S + w'`, which carries its top and bottom
1167
+ * edges along the line and the skin and needs no read of its own height.
1168
+ * `mouth_teeth`, its rows above `Tu` thinned, moves by the line's travel and
1169
+ * `mouth_tongue` by the interior's bottom edge's, each one number per column.
1170
+ */
1171
+ declare function mouthFold(role: LipRole | LipInsideRole, opening: Opening): (col: number, y: number, v: number) => [number, number];
1172
+ /** A lip's dy at MouthOpen 0 at a canvas pixel `(col, row)` as the mesh
1173
+ * renders it, for the composer's closed preview: read at the pixel's centre
1174
+ * off the STORED offsets (the numbers the `.iki` carries, rounded as
1175
+ * `localWarp` rounds them) of the `columnMesh` triangle containing it — the
1176
+ * cell by knot and row, its triangle by the cell's top-left–bottom-right
1177
+ * diagonal, the three vertices weighted barycentrically. The thin and the
1178
+ * taper move a column's rows by different amounts and the renderer is
1179
+ * linear over a triangle, never bilinear over a cell, so no one row stands
1180
+ * for the column. `undefined` for a pixel centre outside the part's box (the
1181
+ * mesh's domain), which has no field. `byRole` must be the layers as the rig
1182
+ * measures them (the canvas-sized ones): a box measured on a smaller frame
1183
+ * clamps differently, and the knot grid starts at the union's edge. */
1184
+ declare function mouthRestShift(byRole: Map<string, LayerInput>): (role: "lip_upper" | "lip_lower", col: number, row: number) => number | undefined;
1185
+
1010
1186
  /**
1011
1187
  * `generateIkiFromLayerSet`: role layers in, a rigged `.iki` out.
1012
1188
  *
@@ -1026,7 +1202,8 @@ declare function parseLayerRoles(fileNames: string[]): {
1026
1202
  * the default expressions and the Nod, Shake and Tilt motions, and
1027
1203
  * with a pose forearm the Wave (`animations.ts`). The mouth has two paths,
1028
1204
  * chosen by the layers: `mouth` alone stretches, with `mouth_open` it
1029
- * crossfades; the lip set folds open like the eyelid (`mouth.ts`).
1205
+ * crossfades; the lip set folds open like the eyelid (`mouth.ts`), its
1206
+ * optional `mouth_tongue` and `mouth_teeth` clipped to its interior.
1030
1207
  */
1031
1208
 
1032
1209
  declare function generateIkiFromLayerSet(layers: LayerInput[], canvas: {
@@ -1424,4 +1601,4 @@ declare function createGridMesh(cols: number, rows: number): IkiMesh;
1424
1601
  */
1425
1602
  declare function createDefaultWarpDeformer(model: IkiModel): IkiWarpDeformer;
1426
1603
 
1427
- 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, LayerGeometryError, type LayerInput, type LayerSetMeasurement, type LayerSetMeasurer, 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, computeGridOffsets, createDefaultMatrixDeformer, createDefaultPart, createDefaultWarpDeformer, createGridMesh, createLayerSetMeasurer, denseCoreOf, detectAlphaBbox, forearmPoseGeometry, foregroundSpan, generateIkiFromLayerSet, headHalfOf, interpolateGridOffsets, isSpeckCore, packAtlas, parseLayerRoles, partIdsOfRole, upsertGridKeyform, uvRectFor, validateDeformerDelete, validateDeformerReparent, validatePartAttach };
1604
+ 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 };