@ikijs/editor 0.5.0 → 0.7.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 +38 -7
- package/dist/index.d.mts +140 -13
- package/dist/index.d.ts +140 -13
- package/dist/index.js +1634 -711
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1634 -711
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,12 +72,29 @@ slides, how much the far eye foreshortens, whether the silhouette holds),
|
|
|
72
72
|
defaulting to `DEFAULT_TURN_TARGETS`. The features' slide is bounded by the art:
|
|
73
73
|
every feature has to stay on the face plate, because past its contour the far
|
|
74
74
|
eye is drawn over the side hair, which still bends with the plate inside that
|
|
75
|
-
held outline and swallows it.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
held outline and swallows it. Given `options.strandEdges`, the eyes are also
|
|
76
|
+
kept from sliding the far iris under the bangs' side strand any further than it
|
|
77
|
+
is painted — best effort, never a refusal: the fit prefers radii where some eye
|
|
78
|
+
depth holds that, and where none does (a fringe spanning the face included) the
|
|
79
|
+
rig is still built with the eyes' turn depth at 0. The report's `strandOverlap`
|
|
80
|
+
gives, per side whose far iris the bangs' run still covers at some far stop,
|
|
81
|
+
the covered width: how much of the iris's painted row the run covers at the
|
|
82
|
+
stop where that is largest, in px and head half-widths, next to how much it
|
|
83
|
+
covers at rest, and whether the bound held — `true` for an iris painted under
|
|
84
|
+
its run that the turn took no deeper, `false` where the fitted radius could not
|
|
85
|
+
hold it and the eyes' turn depth is 0.
|
|
86
|
+
|
|
87
|
+
A target that came from the defaults is a style prior, not a measurement of
|
|
88
|
+
this character, so it is **clamped** to what the layer set can do and the rig is
|
|
89
|
+
built. So is an `eyeShift` you passed that runs past the room the art leaves the
|
|
90
|
+
far eye — the face plate's edge, or the bangs' strand — since that room is a
|
|
91
|
+
fact about these layers, not about the reference. Every other target you passed
|
|
92
|
+
that this layer set cannot reach **throws**, naming the field and the range it
|
|
93
|
+
could have had: a `farEyeRatio` or `silhouetteRatio` no radius renders, a
|
|
94
|
+
`noseShift` / `mouthShift` outside what that feature can reach, or an
|
|
95
|
+
`eyeShift` smaller than the slide the face's own turn already gives the eyes.
|
|
96
|
+
Pass `options.onTurnSolved` to see what the turn settled on and which targets
|
|
97
|
+
were clamped.
|
|
81
98
|
|
|
82
99
|
`turnTargets.headHalfWidth` is what the shift targets are fractions of, when a
|
|
83
100
|
caller has measured the actual head (the face plate stands in otherwise).
|
|
@@ -89,10 +106,24 @@ hair holds still, a face-plate or body edge slide) rather than assume the head
|
|
|
89
106
|
is centred on the face plate or that whichever part drew furthest out at rest
|
|
90
107
|
is still the furthest out after it turns.
|
|
91
108
|
|
|
109
|
+
`options.strandEdges` gives, per side, that side's iris and the `hair_front`
|
|
110
|
+
run it would slide under on the turn, as real pixel edges on the row holding
|
|
111
|
+
the iris's centre (`IrisStrand`: the iris's opaque span, and the run's outer
|
|
112
|
+
and face-side ends). One shape covers a strand outward of a clear iris, a run
|
|
113
|
+
over the iris centre that clears on the face side, and a fringe spanning the
|
|
114
|
+
face, whose `runFace` is `null` because it has no face-side end on that side.
|
|
115
|
+
`@ikijs/mcp` measures it off the pixels it decodes. It is always validated
|
|
116
|
+
against the layers, but like `headEdges` it is only read by the turn solve, so
|
|
117
|
+
it has no effect on the rig without a `nose`; absent or `{}`, the rig is exactly
|
|
118
|
+
the one built without it.
|
|
119
|
+
|
|
92
120
|
It takes **already-decoded** layer geometry (`LayerInput`), never pixels, which
|
|
93
121
|
is what keeps this package free of any image dependency: the editor app
|
|
94
122
|
decodes with canvas, `@ikijs/mcp` decodes with `sharp`, and both feed the same
|
|
95
|
-
pure function
|
|
123
|
+
pure function — including the optional `rowHalfWidths` (one entry per crop row,
|
|
124
|
+
half that row's opaque span in canvas px), which `@ikijs/mcp` measures for the
|
|
125
|
+
face so the plate turns on a radius that varies by row; absent, it turns on one
|
|
126
|
+
constant radius.
|
|
96
127
|
|
|
97
128
|
`*_L` / `*_R` are the **character's** sides — `eye_L` is the character's left
|
|
98
129
|
eye, which appears on the viewer's right.
|
package/dist/index.d.mts
CHANGED
|
@@ -875,6 +875,19 @@ interface LayerInput {
|
|
|
875
875
|
cropW: number;
|
|
876
876
|
/** Cropped image height = bbox.h. */
|
|
877
877
|
cropH: number;
|
|
878
|
+
/**
|
|
879
|
+
* The layer's painted half-width on every CROP row, top → bottom, one entry
|
|
880
|
+
* per row (`cropH` of them): half the row's opaque span in canvas px under
|
|
881
|
+
* the same alpha ≥ 128 rule the head's `headEdges` are measured with
|
|
882
|
+
* (`@ikijs/mcp`'s `measure-turn`), 0 for a row with no opaque pixel.
|
|
883
|
+
* Optional, and only the FACE's is read: it gives the face plate a
|
|
884
|
+
* row-dependent turn radius and a chin swing (`faceRowProfile`,
|
|
885
|
+
* `turnSurface`); absent — as the editor's own import leaves it — the plate
|
|
886
|
+
* turns on one radius on every row. Validated before anything reads it: an
|
|
887
|
+
* array of length `cropH` whose every entry is a finite number in
|
|
888
|
+
* [0, cropW / 2], the most an inclusive span inside the crop can be.
|
|
889
|
+
*/
|
|
890
|
+
rowHalfWidths?: number[];
|
|
878
891
|
}
|
|
879
892
|
/**
|
|
880
893
|
* Map an array of raw filenames to canonical `{ role, fileName }` pairs.
|
|
@@ -891,6 +904,40 @@ declare function parseLayerRoles(fileNames: string[]): {
|
|
|
891
904
|
role: string;
|
|
892
905
|
fileName: string;
|
|
893
906
|
}[];
|
|
907
|
+
/**
|
|
908
|
+
* One side's iris and the bangs' run it would slide under on the turn, as
|
|
909
|
+
* real pixel boundaries on ONE image row — the row holding that iris's centre
|
|
910
|
+
* — in model coordinates: x is canvas x minus half the canvas width, like
|
|
911
|
+
* `bboxToTransform`'s crop edges, and `y` is that row's centre. Every x is the
|
|
912
|
+
* painted edge facing its clear neighbour, read under one opacity rule, so the
|
|
913
|
+
* gap between an iris edge and a run edge is a real distance with no margin to
|
|
914
|
+
* add. "Outer" is toward the head's edge on that side. The run is the one a
|
|
915
|
+
* producer judges a strand: it may skip runs it judges hair detail
|
|
916
|
+
* (`@ikijs/mcp` treats those narrower than half the iris's painted width on
|
|
917
|
+
* the row as clear), so an iris under only such a wisp counts as clear.
|
|
918
|
+
*
|
|
919
|
+
* One shape covers three cases:
|
|
920
|
+
* - an ordinary side strand outward of a clear iris (`runFace` outward of
|
|
921
|
+
* `irisOuter`);
|
|
922
|
+
* - a run over the iris centre that clears on the face side (`runFace`
|
|
923
|
+
* inward of that centre);
|
|
924
|
+
* - a fringe spanning the face: the run still covers the OTHER iris's
|
|
925
|
+
* centre, so it has no face-side end on this side and `runFace` is
|
|
926
|
+
* `null` — an absent edge, not an invented one.
|
|
927
|
+
*/
|
|
928
|
+
interface IrisStrand {
|
|
929
|
+
/** The iris row's centre, model y. */
|
|
930
|
+
y: number;
|
|
931
|
+
/** The outer end of that iris layer's opaque span on the row. */
|
|
932
|
+
irisOuter: number;
|
|
933
|
+
/** Its inner end, toward the face. */
|
|
934
|
+
irisInner: number;
|
|
935
|
+
/** The outer end of the `hair_front` run the iris would slide under. */
|
|
936
|
+
runOuter: number;
|
|
937
|
+
/** The run's face-side end, or `null` when it reaches the other iris's
|
|
938
|
+
* centre. */
|
|
939
|
+
runFace: number | null;
|
|
940
|
+
}
|
|
894
941
|
/**
|
|
895
942
|
* What a head does at full turn, as the cues `measure_turn_reference` reads off
|
|
896
943
|
* a front/turned reference pair. The turn is fitted to these — the cylinder's
|
|
@@ -908,6 +955,14 @@ declare function parseLayerRoles(fileNames: string[]): {
|
|
|
908
955
|
* layer set can do and the rig is built. The alternative is a generator that
|
|
909
956
|
* refuses its own defaults, which would leave a perfectly good layer set with
|
|
910
957
|
* no model at all. `solveTurnModel` reports which fields it clamped.
|
|
958
|
+
*
|
|
959
|
+
* One measurement is clamped too: an `eyeShift` past the room the art leaves
|
|
960
|
+
* the far eye — the face plate's edge, and the bangs' side strand when
|
|
961
|
+
* `strandEdges` is given — whoever passed it. That room is a fact about this
|
|
962
|
+
* layer set, not about the reference the shift was measured on, so the rig
|
|
963
|
+
* takes as much of the shift as the art allows and keeps fitting the other
|
|
964
|
+
* cues. An `eyeShift` below the slide the face's own turn already gives the
|
|
965
|
+
* eyes is still refused: no depth reaches it.
|
|
911
966
|
*/
|
|
912
967
|
interface TurnTargets {
|
|
913
968
|
/** How far the eye pair's centre slides toward the far side at full turn, as
|
|
@@ -973,9 +1028,43 @@ interface TurnSolveReport {
|
|
|
973
1028
|
farEyeRatio: number;
|
|
974
1029
|
silhouetteRatio: number;
|
|
975
1030
|
};
|
|
976
|
-
/**
|
|
977
|
-
*
|
|
1031
|
+
/** The targets the rig fell short of, cut down to what it can reach: every
|
|
1032
|
+
* defaulted field it could not reach, and an `eyeShift` — the caller's own
|
|
1033
|
+
* too — past the room the art leaves the far eye (the face plate's edge,
|
|
1034
|
+
* and the bangs' strand when `strandEdges` was given). Empty when every
|
|
1035
|
+
* target was met. */
|
|
978
1036
|
clamped: (keyof TurnTargets)[];
|
|
1037
|
+
/** Per side whose far iris the bangs' run covers at some far stop at the
|
|
1038
|
+
* fitted radius and depth (`options.strandEdges`): how much of it. Absent
|
|
1039
|
+
* when no side has an entry. Never a clamp: `clamped` still names an
|
|
1040
|
+
* `eyeShift` the strand bound cut short. */
|
|
1041
|
+
strandOverlap?: {
|
|
1042
|
+
left?: StrandOverlap;
|
|
1043
|
+
right?: StrandOverlap;
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
/**
|
|
1047
|
+
* How much of one side's far iris the bangs' run covers: the length of the
|
|
1048
|
+
* landed iris span `[irisOuter, irisInner]` on its row that lies inside the
|
|
1049
|
+
* landed run `[runOuter, runFace]` — face-ward without end for a run with no
|
|
1050
|
+
* face-side edge (`runFace: null`) — at the far stop where that is largest.
|
|
1051
|
+
* Always read off real landed boundaries (`IrisStrand`).
|
|
1052
|
+
*/
|
|
1053
|
+
interface StrandOverlap {
|
|
1054
|
+
/** The far stop the covered width is largest at, degrees. */
|
|
1055
|
+
deg: number;
|
|
1056
|
+
/** That covered width, px. */
|
|
1057
|
+
px: number;
|
|
1058
|
+
/** `px` over the head half-width the shift cues are fractions of. */
|
|
1059
|
+
hh: number;
|
|
1060
|
+
/** The covered width as painted, at rest, px. */
|
|
1061
|
+
restPx: number;
|
|
1062
|
+
/** Whether this side's strand bound held at the fitted depth — the iris
|
|
1063
|
+
* kept no deeper under the run than it is painted, at every far stop. When
|
|
1064
|
+
* false the fitted radius could not hold it (always so for `runFace:
|
|
1065
|
+
* null`), the eyes' turn depth is 0, and `px` is the coverage that is
|
|
1066
|
+
* left. */
|
|
1067
|
+
held: boolean;
|
|
979
1068
|
}
|
|
980
1069
|
/**
|
|
981
1070
|
* Auto-rig: given decoded layer inputs and the shared canvas size, produce a
|
|
@@ -983,16 +1072,23 @@ interface TurnSolveReport {
|
|
|
983
1072
|
*
|
|
984
1073
|
* - Validate all inputs before deriving anything.
|
|
985
1074
|
* - Place parts at source-derived positions (bboxToTransform, unshifted).
|
|
986
|
-
* - Emit the standard parameters (
|
|
987
|
-
*
|
|
988
|
-
*
|
|
1075
|
+
* - Emit the standard parameters (STANDARD_PARAMETERS), plus a conditional
|
|
1076
|
+
* HairSwayX/Z pair + hair-sway physics rigs when a hair_front layer is
|
|
1077
|
+
* present.
|
|
1078
|
+
* - Size every turn group's grid to its own members and the virtual lattice
|
|
1079
|
+
* to those grids, then solve the turn on them (turnSetup, solveTurnModel):
|
|
1080
|
+
* one surface — radius, travel, the head-level nod radius — that every
|
|
1081
|
+
* bake below samples.
|
|
989
1082
|
* - Build headDeformer (matrix, neck pivot, nod/tilt/breath bindings — the
|
|
990
|
-
* turn translates nothing rigidly; its travel is in the
|
|
1083
|
+
* turn translates nothing rigidly; its travel is in the group grids), one
|
|
1084
|
+
* warp deformer per turn group present — `faceWarp` for the plate,
|
|
1085
|
+
* `eyeWarp_L/R`, `mouthWarp`, `noseWarp`, `browWarp_L/R`, `blushWarp_L/R`
|
|
1086
|
+
* — each a warp2d over its own grid baked from that surface with the
|
|
1087
|
+
* family's solved depth as keyform geometry (bakeTurnGroupWarp2D), and
|
|
991
1088
|
* bodyDeformer when a body layer is present (matrix, torso-base pivot,
|
|
992
|
-
* AngleX follow + Breath follow)
|
|
993
|
-
* baked cylinder warp center-relative on faceCenterX).
|
|
1089
|
+
* AngleX follow + Breath follow).
|
|
994
1090
|
* - Mesh parts (spec.mesh===true) → width:1, height:1, pixel grid mesh sized by
|
|
995
|
-
* meshCellsFor + role bindings.
|
|
1091
|
+
* meshCellsFor + role bindings, each face-family part on its group's warp.
|
|
996
1092
|
* - Static parts (spec.mesh===false) → width:cropW, height:cropH, no mesh.
|
|
997
1093
|
* - Part ids equal the role string (deterministic, no crypto.randomUUID).
|
|
998
1094
|
* - Return parseIkiModel(structuredClone(model)) — every caller gets a
|
|
@@ -1001,9 +1097,17 @@ interface TurnSolveReport {
|
|
|
1001
1097
|
* `options.turnTargets` is what the head turn is fitted to: the cues a 30°
|
|
1002
1098
|
* reference measures, defaulting to DEFAULT_TURN_TARGETS. A target the CALLER
|
|
1003
1099
|
* passed that this layer set cannot reach throws; one that came from the
|
|
1004
|
-
* defaults is clamped to what it can do
|
|
1005
|
-
*
|
|
1006
|
-
* what the turn
|
|
1100
|
+
* defaults is clamped to what it can do, and so is a caller's `eyeShift` past
|
|
1101
|
+
* the room the art leaves the far eye — see TurnTargets for why, and pass
|
|
1102
|
+
* `options.onTurnSolved` to see which ones were clamped and what the turn
|
|
1103
|
+
* ended up reaching.
|
|
1104
|
+
*
|
|
1105
|
+
* `options.strandEdges` gives each side's iris and the bangs' run it would
|
|
1106
|
+
* slide under as measured pixel edges (`IrisStrand`), and with it the turn
|
|
1107
|
+
* keeps each far iris from sliding under that run any further than it is
|
|
1108
|
+
* painted, best effort: where no radius the fit can use holds that, the rig
|
|
1109
|
+
* is still built, with the eyes' depth at 0, and the report's
|
|
1110
|
+
* `strandOverlap` says how much of the iris the bangs cover.
|
|
1007
1111
|
*/
|
|
1008
1112
|
declare function generateIkiFromLayerSet(layers: LayerInput[], canvas: {
|
|
1009
1113
|
width: number;
|
|
@@ -1027,6 +1131,29 @@ declare function generateIkiFromLayerSet(layers: LayerInput[], canvas: {
|
|
|
1027
1131
|
x: number;
|
|
1028
1132
|
}[];
|
|
1029
1133
|
};
|
|
1134
|
+
/** Per side (`left` the −x side, `right` the +x side, like `headEdges`),
|
|
1135
|
+
* that side's iris and the bangs' run it would slide under on the turn,
|
|
1136
|
+
* as real pixel edges on the row holding the iris's centre — see
|
|
1137
|
+
* `IrisStrand` for the contract and its three cases (a strand outward of
|
|
1138
|
+
* a clear iris, a run over the iris centre that clears on the face side,
|
|
1139
|
+
* a fringe spanning the face). A side's iris is whichever of `iris_L` /
|
|
1140
|
+
* `iris_R` sits on that side; the sides are independent, so either may
|
|
1141
|
+
* be absent. With it the turn keeps each far iris from sliding under its
|
|
1142
|
+
* run any further than it is painted, best effort: never a refusal. The
|
|
1143
|
+
* report's `strandOverlap` gives, per side whose far iris the run still
|
|
1144
|
+
* covers at some far stop, the covered width at the stop where it is
|
|
1145
|
+
* largest, next to the painted one, and whether the bound held — `true`
|
|
1146
|
+
* for an iris painted under its run that the turn took no deeper,
|
|
1147
|
+
* `false` where the fitted radius could not hold it and the eyes' turn
|
|
1148
|
+
* depth is 0. It is always
|
|
1149
|
+
* validated against the layers first (`validateStrandEdges`), but only
|
|
1150
|
+
* the turn solve reads it, so like `headEdges` it has no effect on the
|
|
1151
|
+
* rig without a nose; absent or `{}`, the rig is the one built without
|
|
1152
|
+
* it. */
|
|
1153
|
+
strandEdges?: {
|
|
1154
|
+
left?: IrisStrand;
|
|
1155
|
+
right?: IrisStrand;
|
|
1156
|
+
};
|
|
1030
1157
|
}): IkiModel;
|
|
1031
1158
|
|
|
1032
|
-
export { ALPHA_BBOX_THRESHOLD, ATLAS_PADDING, AddDeformer, AddPart, AddPhysicsRig, type AlphaBbox, type ApplyAtlasInput, type AtlasAssignment, type AtlasLayout, type AtlasPlacement, type AtlasSource, CaptureGridKeyform, DEFAULT_TURN_TARGETS, type DeformerTransformChannel, DeleteDeformer, DeletePart, DeletePhysicsRig, type EditCommand, type EditTransformChannel, EditorDocument, type LayerInput, SetDeformerBindings, SetDeformerParent, SetDeformerPivot, SetDeformerPivotX, SetDeformerPivotY, SetDeformerTransform, SetPartBindings, SetPartColor, SetPartDeformer, SetPartHeight, SetPartMesh, SetPartOrder, SetPartTransform, SetPartWidth, SetPhysicsRig, type TurnDepths, type TurnSolveReport, TurnTargetError, type TurnTargets, UV_INSET_PX, captureBindingEndpoint, computeGridOffsets, createDefaultMatrixDeformer, createDefaultPart, createDefaultWarpDeformer, createGridMesh, detectAlphaBbox, generateIkiFromLayerSet, interpolateGridOffsets, packAtlas, parseLayerRoles, upsertGridKeyform, uvRectFor, validateDeformerDelete, validateDeformerReparent, validatePartAttach };
|
|
1159
|
+
export { ALPHA_BBOX_THRESHOLD, ATLAS_PADDING, AddDeformer, AddPart, AddPhysicsRig, type AlphaBbox, type ApplyAtlasInput, type AtlasAssignment, type AtlasLayout, type AtlasPlacement, type AtlasSource, CaptureGridKeyform, DEFAULT_TURN_TARGETS, type DeformerTransformChannel, DeleteDeformer, DeletePart, DeletePhysicsRig, type EditCommand, type EditTransformChannel, EditorDocument, type IrisStrand, type LayerInput, 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, captureBindingEndpoint, computeGridOffsets, createDefaultMatrixDeformer, createDefaultPart, createDefaultWarpDeformer, createGridMesh, detectAlphaBbox, generateIkiFromLayerSet, interpolateGridOffsets, packAtlas, parseLayerRoles, upsertGridKeyform, uvRectFor, validateDeformerDelete, validateDeformerReparent, validatePartAttach };
|
package/dist/index.d.ts
CHANGED
|
@@ -875,6 +875,19 @@ interface LayerInput {
|
|
|
875
875
|
cropW: number;
|
|
876
876
|
/** Cropped image height = bbox.h. */
|
|
877
877
|
cropH: number;
|
|
878
|
+
/**
|
|
879
|
+
* The layer's painted half-width on every CROP row, top → bottom, one entry
|
|
880
|
+
* per row (`cropH` of them): half the row's opaque span in canvas px under
|
|
881
|
+
* the same alpha ≥ 128 rule the head's `headEdges` are measured with
|
|
882
|
+
* (`@ikijs/mcp`'s `measure-turn`), 0 for a row with no opaque pixel.
|
|
883
|
+
* Optional, and only the FACE's is read: it gives the face plate a
|
|
884
|
+
* row-dependent turn radius and a chin swing (`faceRowProfile`,
|
|
885
|
+
* `turnSurface`); absent — as the editor's own import leaves it — the plate
|
|
886
|
+
* turns on one radius on every row. Validated before anything reads it: an
|
|
887
|
+
* array of length `cropH` whose every entry is a finite number in
|
|
888
|
+
* [0, cropW / 2], the most an inclusive span inside the crop can be.
|
|
889
|
+
*/
|
|
890
|
+
rowHalfWidths?: number[];
|
|
878
891
|
}
|
|
879
892
|
/**
|
|
880
893
|
* Map an array of raw filenames to canonical `{ role, fileName }` pairs.
|
|
@@ -891,6 +904,40 @@ declare function parseLayerRoles(fileNames: string[]): {
|
|
|
891
904
|
role: string;
|
|
892
905
|
fileName: string;
|
|
893
906
|
}[];
|
|
907
|
+
/**
|
|
908
|
+
* One side's iris and the bangs' run it would slide under on the turn, as
|
|
909
|
+
* real pixel boundaries on ONE image row — the row holding that iris's centre
|
|
910
|
+
* — in model coordinates: x is canvas x minus half the canvas width, like
|
|
911
|
+
* `bboxToTransform`'s crop edges, and `y` is that row's centre. Every x is the
|
|
912
|
+
* painted edge facing its clear neighbour, read under one opacity rule, so the
|
|
913
|
+
* gap between an iris edge and a run edge is a real distance with no margin to
|
|
914
|
+
* add. "Outer" is toward the head's edge on that side. The run is the one a
|
|
915
|
+
* producer judges a strand: it may skip runs it judges hair detail
|
|
916
|
+
* (`@ikijs/mcp` treats those narrower than half the iris's painted width on
|
|
917
|
+
* the row as clear), so an iris under only such a wisp counts as clear.
|
|
918
|
+
*
|
|
919
|
+
* One shape covers three cases:
|
|
920
|
+
* - an ordinary side strand outward of a clear iris (`runFace` outward of
|
|
921
|
+
* `irisOuter`);
|
|
922
|
+
* - a run over the iris centre that clears on the face side (`runFace`
|
|
923
|
+
* inward of that centre);
|
|
924
|
+
* - a fringe spanning the face: the run still covers the OTHER iris's
|
|
925
|
+
* centre, so it has no face-side end on this side and `runFace` is
|
|
926
|
+
* `null` — an absent edge, not an invented one.
|
|
927
|
+
*/
|
|
928
|
+
interface IrisStrand {
|
|
929
|
+
/** The iris row's centre, model y. */
|
|
930
|
+
y: number;
|
|
931
|
+
/** The outer end of that iris layer's opaque span on the row. */
|
|
932
|
+
irisOuter: number;
|
|
933
|
+
/** Its inner end, toward the face. */
|
|
934
|
+
irisInner: number;
|
|
935
|
+
/** The outer end of the `hair_front` run the iris would slide under. */
|
|
936
|
+
runOuter: number;
|
|
937
|
+
/** The run's face-side end, or `null` when it reaches the other iris's
|
|
938
|
+
* centre. */
|
|
939
|
+
runFace: number | null;
|
|
940
|
+
}
|
|
894
941
|
/**
|
|
895
942
|
* What a head does at full turn, as the cues `measure_turn_reference` reads off
|
|
896
943
|
* a front/turned reference pair. The turn is fitted to these — the cylinder's
|
|
@@ -908,6 +955,14 @@ declare function parseLayerRoles(fileNames: string[]): {
|
|
|
908
955
|
* layer set can do and the rig is built. The alternative is a generator that
|
|
909
956
|
* refuses its own defaults, which would leave a perfectly good layer set with
|
|
910
957
|
* no model at all. `solveTurnModel` reports which fields it clamped.
|
|
958
|
+
*
|
|
959
|
+
* One measurement is clamped too: an `eyeShift` past the room the art leaves
|
|
960
|
+
* the far eye — the face plate's edge, and the bangs' side strand when
|
|
961
|
+
* `strandEdges` is given — whoever passed it. That room is a fact about this
|
|
962
|
+
* layer set, not about the reference the shift was measured on, so the rig
|
|
963
|
+
* takes as much of the shift as the art allows and keeps fitting the other
|
|
964
|
+
* cues. An `eyeShift` below the slide the face's own turn already gives the
|
|
965
|
+
* eyes is still refused: no depth reaches it.
|
|
911
966
|
*/
|
|
912
967
|
interface TurnTargets {
|
|
913
968
|
/** How far the eye pair's centre slides toward the far side at full turn, as
|
|
@@ -973,9 +1028,43 @@ interface TurnSolveReport {
|
|
|
973
1028
|
farEyeRatio: number;
|
|
974
1029
|
silhouetteRatio: number;
|
|
975
1030
|
};
|
|
976
|
-
/**
|
|
977
|
-
*
|
|
1031
|
+
/** The targets the rig fell short of, cut down to what it can reach: every
|
|
1032
|
+
* defaulted field it could not reach, and an `eyeShift` — the caller's own
|
|
1033
|
+
* too — past the room the art leaves the far eye (the face plate's edge,
|
|
1034
|
+
* and the bangs' strand when `strandEdges` was given). Empty when every
|
|
1035
|
+
* target was met. */
|
|
978
1036
|
clamped: (keyof TurnTargets)[];
|
|
1037
|
+
/** Per side whose far iris the bangs' run covers at some far stop at the
|
|
1038
|
+
* fitted radius and depth (`options.strandEdges`): how much of it. Absent
|
|
1039
|
+
* when no side has an entry. Never a clamp: `clamped` still names an
|
|
1040
|
+
* `eyeShift` the strand bound cut short. */
|
|
1041
|
+
strandOverlap?: {
|
|
1042
|
+
left?: StrandOverlap;
|
|
1043
|
+
right?: StrandOverlap;
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
/**
|
|
1047
|
+
* How much of one side's far iris the bangs' run covers: the length of the
|
|
1048
|
+
* landed iris span `[irisOuter, irisInner]` on its row that lies inside the
|
|
1049
|
+
* landed run `[runOuter, runFace]` — face-ward without end for a run with no
|
|
1050
|
+
* face-side edge (`runFace: null`) — at the far stop where that is largest.
|
|
1051
|
+
* Always read off real landed boundaries (`IrisStrand`).
|
|
1052
|
+
*/
|
|
1053
|
+
interface StrandOverlap {
|
|
1054
|
+
/** The far stop the covered width is largest at, degrees. */
|
|
1055
|
+
deg: number;
|
|
1056
|
+
/** That covered width, px. */
|
|
1057
|
+
px: number;
|
|
1058
|
+
/** `px` over the head half-width the shift cues are fractions of. */
|
|
1059
|
+
hh: number;
|
|
1060
|
+
/** The covered width as painted, at rest, px. */
|
|
1061
|
+
restPx: number;
|
|
1062
|
+
/** Whether this side's strand bound held at the fitted depth — the iris
|
|
1063
|
+
* kept no deeper under the run than it is painted, at every far stop. When
|
|
1064
|
+
* false the fitted radius could not hold it (always so for `runFace:
|
|
1065
|
+
* null`), the eyes' turn depth is 0, and `px` is the coverage that is
|
|
1066
|
+
* left. */
|
|
1067
|
+
held: boolean;
|
|
979
1068
|
}
|
|
980
1069
|
/**
|
|
981
1070
|
* Auto-rig: given decoded layer inputs and the shared canvas size, produce a
|
|
@@ -983,16 +1072,23 @@ interface TurnSolveReport {
|
|
|
983
1072
|
*
|
|
984
1073
|
* - Validate all inputs before deriving anything.
|
|
985
1074
|
* - Place parts at source-derived positions (bboxToTransform, unshifted).
|
|
986
|
-
* - Emit the standard parameters (
|
|
987
|
-
*
|
|
988
|
-
*
|
|
1075
|
+
* - Emit the standard parameters (STANDARD_PARAMETERS), plus a conditional
|
|
1076
|
+
* HairSwayX/Z pair + hair-sway physics rigs when a hair_front layer is
|
|
1077
|
+
* present.
|
|
1078
|
+
* - Size every turn group's grid to its own members and the virtual lattice
|
|
1079
|
+
* to those grids, then solve the turn on them (turnSetup, solveTurnModel):
|
|
1080
|
+
* one surface — radius, travel, the head-level nod radius — that every
|
|
1081
|
+
* bake below samples.
|
|
989
1082
|
* - Build headDeformer (matrix, neck pivot, nod/tilt/breath bindings — the
|
|
990
|
-
* turn translates nothing rigidly; its travel is in the
|
|
1083
|
+
* turn translates nothing rigidly; its travel is in the group grids), one
|
|
1084
|
+
* warp deformer per turn group present — `faceWarp` for the plate,
|
|
1085
|
+
* `eyeWarp_L/R`, `mouthWarp`, `noseWarp`, `browWarp_L/R`, `blushWarp_L/R`
|
|
1086
|
+
* — each a warp2d over its own grid baked from that surface with the
|
|
1087
|
+
* family's solved depth as keyform geometry (bakeTurnGroupWarp2D), and
|
|
991
1088
|
* bodyDeformer when a body layer is present (matrix, torso-base pivot,
|
|
992
|
-
* AngleX follow + Breath follow)
|
|
993
|
-
* baked cylinder warp center-relative on faceCenterX).
|
|
1089
|
+
* AngleX follow + Breath follow).
|
|
994
1090
|
* - Mesh parts (spec.mesh===true) → width:1, height:1, pixel grid mesh sized by
|
|
995
|
-
* meshCellsFor + role bindings.
|
|
1091
|
+
* meshCellsFor + role bindings, each face-family part on its group's warp.
|
|
996
1092
|
* - Static parts (spec.mesh===false) → width:cropW, height:cropH, no mesh.
|
|
997
1093
|
* - Part ids equal the role string (deterministic, no crypto.randomUUID).
|
|
998
1094
|
* - Return parseIkiModel(structuredClone(model)) — every caller gets a
|
|
@@ -1001,9 +1097,17 @@ interface TurnSolveReport {
|
|
|
1001
1097
|
* `options.turnTargets` is what the head turn is fitted to: the cues a 30°
|
|
1002
1098
|
* reference measures, defaulting to DEFAULT_TURN_TARGETS. A target the CALLER
|
|
1003
1099
|
* passed that this layer set cannot reach throws; one that came from the
|
|
1004
|
-
* defaults is clamped to what it can do
|
|
1005
|
-
*
|
|
1006
|
-
* what the turn
|
|
1100
|
+
* defaults is clamped to what it can do, and so is a caller's `eyeShift` past
|
|
1101
|
+
* the room the art leaves the far eye — see TurnTargets for why, and pass
|
|
1102
|
+
* `options.onTurnSolved` to see which ones were clamped and what the turn
|
|
1103
|
+
* ended up reaching.
|
|
1104
|
+
*
|
|
1105
|
+
* `options.strandEdges` gives each side's iris and the bangs' run it would
|
|
1106
|
+
* slide under as measured pixel edges (`IrisStrand`), and with it the turn
|
|
1107
|
+
* keeps each far iris from sliding under that run any further than it is
|
|
1108
|
+
* painted, best effort: where no radius the fit can use holds that, the rig
|
|
1109
|
+
* is still built, with the eyes' depth at 0, and the report's
|
|
1110
|
+
* `strandOverlap` says how much of the iris the bangs cover.
|
|
1007
1111
|
*/
|
|
1008
1112
|
declare function generateIkiFromLayerSet(layers: LayerInput[], canvas: {
|
|
1009
1113
|
width: number;
|
|
@@ -1027,6 +1131,29 @@ declare function generateIkiFromLayerSet(layers: LayerInput[], canvas: {
|
|
|
1027
1131
|
x: number;
|
|
1028
1132
|
}[];
|
|
1029
1133
|
};
|
|
1134
|
+
/** Per side (`left` the −x side, `right` the +x side, like `headEdges`),
|
|
1135
|
+
* that side's iris and the bangs' run it would slide under on the turn,
|
|
1136
|
+
* as real pixel edges on the row holding the iris's centre — see
|
|
1137
|
+
* `IrisStrand` for the contract and its three cases (a strand outward of
|
|
1138
|
+
* a clear iris, a run over the iris centre that clears on the face side,
|
|
1139
|
+
* a fringe spanning the face). A side's iris is whichever of `iris_L` /
|
|
1140
|
+
* `iris_R` sits on that side; the sides are independent, so either may
|
|
1141
|
+
* be absent. With it the turn keeps each far iris from sliding under its
|
|
1142
|
+
* run any further than it is painted, best effort: never a refusal. The
|
|
1143
|
+
* report's `strandOverlap` gives, per side whose far iris the run still
|
|
1144
|
+
* covers at some far stop, the covered width at the stop where it is
|
|
1145
|
+
* largest, next to the painted one, and whether the bound held — `true`
|
|
1146
|
+
* for an iris painted under its run that the turn took no deeper,
|
|
1147
|
+
* `false` where the fitted radius could not hold it and the eyes' turn
|
|
1148
|
+
* depth is 0. It is always
|
|
1149
|
+
* validated against the layers first (`validateStrandEdges`), but only
|
|
1150
|
+
* the turn solve reads it, so like `headEdges` it has no effect on the
|
|
1151
|
+
* rig without a nose; absent or `{}`, the rig is the one built without
|
|
1152
|
+
* it. */
|
|
1153
|
+
strandEdges?: {
|
|
1154
|
+
left?: IrisStrand;
|
|
1155
|
+
right?: IrisStrand;
|
|
1156
|
+
};
|
|
1030
1157
|
}): IkiModel;
|
|
1031
1158
|
|
|
1032
|
-
export { ALPHA_BBOX_THRESHOLD, ATLAS_PADDING, AddDeformer, AddPart, AddPhysicsRig, type AlphaBbox, type ApplyAtlasInput, type AtlasAssignment, type AtlasLayout, type AtlasPlacement, type AtlasSource, CaptureGridKeyform, DEFAULT_TURN_TARGETS, type DeformerTransformChannel, DeleteDeformer, DeletePart, DeletePhysicsRig, type EditCommand, type EditTransformChannel, EditorDocument, type LayerInput, SetDeformerBindings, SetDeformerParent, SetDeformerPivot, SetDeformerPivotX, SetDeformerPivotY, SetDeformerTransform, SetPartBindings, SetPartColor, SetPartDeformer, SetPartHeight, SetPartMesh, SetPartOrder, SetPartTransform, SetPartWidth, SetPhysicsRig, type TurnDepths, type TurnSolveReport, TurnTargetError, type TurnTargets, UV_INSET_PX, captureBindingEndpoint, computeGridOffsets, createDefaultMatrixDeformer, createDefaultPart, createDefaultWarpDeformer, createGridMesh, detectAlphaBbox, generateIkiFromLayerSet, interpolateGridOffsets, packAtlas, parseLayerRoles, upsertGridKeyform, uvRectFor, validateDeformerDelete, validateDeformerReparent, validatePartAttach };
|
|
1159
|
+
export { ALPHA_BBOX_THRESHOLD, ATLAS_PADDING, AddDeformer, AddPart, AddPhysicsRig, type AlphaBbox, type ApplyAtlasInput, type AtlasAssignment, type AtlasLayout, type AtlasPlacement, type AtlasSource, CaptureGridKeyform, DEFAULT_TURN_TARGETS, type DeformerTransformChannel, DeleteDeformer, DeletePart, DeletePhysicsRig, type EditCommand, type EditTransformChannel, EditorDocument, type IrisStrand, type LayerInput, 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, captureBindingEndpoint, computeGridOffsets, createDefaultMatrixDeformer, createDefaultPart, createDefaultWarpDeformer, createGridMesh, detectAlphaBbox, generateIkiFromLayerSet, interpolateGridOffsets, packAtlas, parseLayerRoles, upsertGridKeyform, uvRectFor, validateDeformerDelete, validateDeformerReparent, validatePartAttach };
|