@ikijs/editor 0.12.0 → 0.12.2
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 +11 -5
- package/dist/index.d.mts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +223 -29
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +223 -29
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @ikijs/editor
|
|
2
2
|
|
|
3
|
+
> Part of [Iki](https://github.com/zeikar/iki), the open Live2D alternative that AI can build — free and MIT-licensed, with an open `.iki` format and a [Claude Code plugin](https://github.com/zeikar/iki/tree/main/plugin) that draws and rigs characters.
|
|
4
|
+
|
|
3
5
|
**Headless** editing core for [`.iki`](https://github.com/zeikar/iki/tree/main/packages/format) models — a document with
|
|
4
6
|
undo/redo, invertible edit commands, atlas layout + UV math, and the auto-rigger.
|
|
5
7
|
|
|
@@ -66,10 +68,12 @@ not a reshaped face. The face plate translates, changing its width by a few
|
|
|
66
68
|
percent, the chin leading it; the features lead the plate by their own amounts
|
|
67
69
|
(the nose most, then the brows, eyes and mouth), the eyes foreshortening mildly
|
|
68
70
|
about their own centres; the front hair rides the face where it lies over it and holds the head's
|
|
69
|
-
outline where it draws it
|
|
71
|
+
outline where it draws it — though on its cap the turn never carries its outer
|
|
72
|
+
edge further outside the back hair painted behind it than the nod alone does —
|
|
73
|
+
and the back hair stays behind; the neck and the
|
|
70
74
|
ears the face layer paints are cut off into islands of the plate's own mesh,
|
|
71
75
|
drawn behind the head — the neck stays (only the chin's shade slides across
|
|
72
|
-
it), the ears lag the face. AngleZ rolls the head 10° about the chin; the torso only
|
|
76
|
+
it), the ears lag the face, the far one narrowing. AngleZ rolls the head 10° about the chin; the torso only
|
|
73
77
|
breathes.
|
|
74
78
|
|
|
75
79
|
On a layer set with a `nose`, `options.turnTargets` — the cues a 30° reference
|
|
@@ -93,9 +97,11 @@ how much it covers at rest, and whether the bound held.
|
|
|
93
97
|
An `eyeShift` you passed that runs past the room the art leaves the far eye —
|
|
94
98
|
the face plate's edge, or the bangs' strand — is **clamped** and the rig is
|
|
95
99
|
built, since that room is a fact about these layers, not about the reference;
|
|
96
|
-
so is the profile's own turn where the art has less room than it needs
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
so is the profile's own turn where the art has less room than it needs, and
|
|
101
|
+
a `silhouetteRatio` that would narrow a plate drawn without hair too far for
|
|
102
|
+
its head to cover the far ear's slide (see AUTO-RIG.md). Every other target
|
|
103
|
+
you passed that this layer set cannot reach **throws**, naming the field and
|
|
104
|
+
the range it could have had: a `farEyeRatio` or `silhouetteRatio`
|
|
99
105
|
out of the rig's reach, or a `noseShift` / `mouthShift` outside what that
|
|
100
106
|
feature can reach. Pass `options.onTurnSolved` to see what the turn settled on
|
|
101
107
|
and which targets were clamped.
|
package/dist/index.d.mts
CHANGED
|
@@ -678,7 +678,10 @@ interface RigStyle {
|
|
|
678
678
|
* `hairFollow` rides it whole). Where back hair does, the edge rides
|
|
679
679
|
* further, as far as that back hair stays behind it through the turn and
|
|
680
680
|
* the nod, up to the front hair's follow there (`hairFollow`; over the
|
|
681
|
-
* far eye's rows, as far as that eye's outer corner goes).
|
|
681
|
+
* far eye's rows, as far as that eye's outer corner goes). On the cap
|
|
682
|
+
* (the rows above the eyes) the edge follows less where the turn would
|
|
683
|
+
* otherwise carry it further outside its back hair than the nod alone
|
|
684
|
+
* does. */
|
|
682
685
|
outlineFollow?: number;
|
|
683
686
|
/** How far the upper lid comes down, over the eye's height (0.30–0.67). */
|
|
684
687
|
blink?: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -678,7 +678,10 @@ interface RigStyle {
|
|
|
678
678
|
* `hairFollow` rides it whole). Where back hair does, the edge rides
|
|
679
679
|
* further, as far as that back hair stays behind it through the turn and
|
|
680
680
|
* the nod, up to the front hair's follow there (`hairFollow`; over the
|
|
681
|
-
* far eye's rows, as far as that eye's outer corner goes).
|
|
681
|
+
* far eye's rows, as far as that eye's outer corner goes). On the cap
|
|
682
|
+
* (the rows above the eyes) the edge follows less where the turn would
|
|
683
|
+
* otherwise carry it further outside its back hair than the nod alone
|
|
684
|
+
* does. */
|
|
682
685
|
outlineFollow?: number;
|
|
683
686
|
/** How far the upper lid comes down, over the eye's height (0.30–0.67). */
|
|
684
687
|
blink?: number;
|
package/dist/index.js
CHANGED
|
@@ -2457,15 +2457,22 @@ var TURN = {
|
|
|
2457
2457
|
* where no back hair paints behind that edge. Where back hair does, the
|
|
2458
2458
|
* edge rides further, as far as that back hair stays behind it through
|
|
2459
2459
|
* the turn and the nod, up to the face's follow there (`hairFollow`;
|
|
2460
|
-
* over the far eye's rows, as far as that eye's outer corner goes).
|
|
2460
|
+
* over the far eye's rows, as far as that eye's outer corner goes). On
|
|
2461
|
+
* the cap (the rows above the eyes) it follows less where the turn would
|
|
2462
|
+
* otherwise carry it further outside its back hair than the nod alone
|
|
2463
|
+
* does. */
|
|
2461
2464
|
outlineFollow: 0,
|
|
2462
2465
|
/** The back hair's slight counter-motion. */
|
|
2463
2466
|
hairBack: -0.027,
|
|
2464
|
-
/** The ears lag the plate: the far one moves this share of
|
|
2465
|
-
* near one this at its widest reach, its root riding the
|
|
2466
|
-
* samples' parallax ratios, 0.44 and 0.87). */
|
|
2467
|
+
/** The ears lag the plate: the far one's outer edge moves this share of
|
|
2468
|
+
* its slide, the near one this at its widest reach, its root riding the
|
|
2469
|
+
* head (the samples' parallax ratios, 0.44 and 0.87). */
|
|
2467
2470
|
earFar: 0.44,
|
|
2468
|
-
earNear: 0.87
|
|
2471
|
+
earNear: 0.87,
|
|
2472
|
+
/** The far ear's width at full turn, about its outer edge, where the head
|
|
2473
|
+
* leaves it room (the samples' median: Haru 0.81 / 0.91, Hiyori 0.90 /
|
|
2474
|
+
* 0.85, Mao 0.77 / 0.79; none fades); less where it does not. */
|
|
2475
|
+
earFarScale: 0.83
|
|
2469
2476
|
};
|
|
2470
2477
|
var NOD = {
|
|
2471
2478
|
faceTop: { up: -0.081, down: 0.178 },
|
|
@@ -2886,16 +2893,45 @@ function earField(m) {
|
|
|
2886
2893
|
const s = Math.sign(ax);
|
|
2887
2894
|
const a = Math.abs(ax) / 30;
|
|
2888
2895
|
const dy = face(x, y, 0, ay)[1];
|
|
2889
|
-
if (sideOf(m, x, s) === "far") return [s * a * TURN.earFar * m.face, dy];
|
|
2890
2896
|
const d = Math.abs(x - f.axisX);
|
|
2891
2897
|
const attach = ears.attachAt(y);
|
|
2898
|
+
const span = Math.max(1, ears.outer - attach);
|
|
2899
|
+
if (sideOf(m, x, s) === "far") {
|
|
2900
|
+
const rim2 = TURN.earFar * m.face;
|
|
2901
|
+
const root2 = earRoot(m, face, y, s);
|
|
2902
|
+
const lead = Math.max(
|
|
2903
|
+
0,
|
|
2904
|
+
Math.min((1 - m.earFarScale) * span, root2 - rim2)
|
|
2905
|
+
);
|
|
2906
|
+
let along = rim2 + lead * (ears.outer - d) / span;
|
|
2907
|
+
if (d < attach) along = Math.min(along, s * face(x, y, 30 * s, 0)[0]);
|
|
2908
|
+
return [s * a * along, dy];
|
|
2909
|
+
}
|
|
2892
2910
|
if (d <= attach) return [face(x, y, ax, 0)[0], dy];
|
|
2893
2911
|
const root = face(f.axisX + Math.sign(x - f.axisX) * attach, y, ax, 0)[0];
|
|
2894
2912
|
const rim = s * a * TURN.earNear * m.face;
|
|
2895
|
-
const t = Math.min(1, (d - attach) /
|
|
2913
|
+
const t = Math.min(1, (d - attach) / span);
|
|
2896
2914
|
return [root + (rim - root) * t, dy];
|
|
2897
2915
|
};
|
|
2898
2916
|
}
|
|
2917
|
+
function earRoot(m, face, y, s) {
|
|
2918
|
+
const f = m.frame;
|
|
2919
|
+
return s * face(f.axisX + s * f.ears.attachAt(y), y, 30 * s, 0)[0];
|
|
2920
|
+
}
|
|
2921
|
+
function farEarCovered(m) {
|
|
2922
|
+
const ears = m.frame.ears;
|
|
2923
|
+
if (ears === void 0) return true;
|
|
2924
|
+
const face = faceField(m);
|
|
2925
|
+
const rim = TURN.earFar * m.face;
|
|
2926
|
+
const y0 = ears.bottom - 2;
|
|
2927
|
+
const y1 = ears.top + 2;
|
|
2928
|
+
const n = Math.max(1, Math.ceil(y1 - y0));
|
|
2929
|
+
for (let i = 0; i <= n; i++) {
|
|
2930
|
+
const y = y0 + (y1 - y0) * i / n;
|
|
2931
|
+
for (const s of [-1, 1]) if (earRoot(m, face, y, s) < rim) return false;
|
|
2932
|
+
}
|
|
2933
|
+
return true;
|
|
2934
|
+
}
|
|
2899
2935
|
function chinSlide(m) {
|
|
2900
2936
|
const f = m.frame;
|
|
2901
2937
|
return Math.abs(faceField(m)(f.axisX, f.chinY, 30, 0)[0]);
|
|
@@ -3038,14 +3074,19 @@ function hairFrontField(m) {
|
|
|
3038
3074
|
);
|
|
3039
3075
|
return core + (m.hairOuter - core) * out;
|
|
3040
3076
|
};
|
|
3041
|
-
const ride = outerRide(m, inner, coreAt, followAt);
|
|
3042
|
-
const followOf = (x, y, s) => followAt(x, y, s) + (ride === void 0 ? 0 : ride(x, y, s));
|
|
3077
|
+
const ride = outerRide(m, inner, coreAt, followAt, eyeY + 1.5 * eyeH);
|
|
3078
|
+
const followOf = (x, y, s) => followAt(x, y, s) + (ride === void 0 ? 0 : ride.lift(x, y, s));
|
|
3043
3079
|
const drift = Math.abs(m.hairBack);
|
|
3044
3080
|
const crown = crownRide(m, cap, (x, y, s) => followOf(x, y, s) + drift);
|
|
3081
|
+
const boundOf = (x, y, s) => {
|
|
3082
|
+
if (ride === void 0) return followOf(x, y, s);
|
|
3083
|
+
const b = ride.blend(y, s);
|
|
3084
|
+
return (1 - b) * (followOf(x, y, s) + ride.cut(x, y, s)) - b * drift;
|
|
3085
|
+
};
|
|
3045
3086
|
return (x, y, ax, ay) => {
|
|
3046
3087
|
const s = Math.sign(ax);
|
|
3047
3088
|
const a = Math.abs(ax) / 30;
|
|
3048
|
-
const rx = s * a *
|
|
3089
|
+
const rx = s * a * boundOf(x, y, s) + a * (m.shellScale - 1) * (x - f.axisX);
|
|
3049
3090
|
const ry = nodDy(NOD.hairFront, ay, f.hh);
|
|
3050
3091
|
const bx = back(x, y, ax, ay)[0];
|
|
3051
3092
|
const by = nodDy(cap, ay, f.hh);
|
|
@@ -3065,6 +3106,18 @@ function gridRow(ys, y, snap = false) {
|
|
|
3065
3106
|
const r = Math.min(n - 2, Math.floor(v));
|
|
3066
3107
|
return { r, t: v - r };
|
|
3067
3108
|
}
|
|
3109
|
+
function gridCol(xs, x) {
|
|
3110
|
+
let c = 0;
|
|
3111
|
+
while (c < xs.length - 2 && xs[c + 1] < x) c++;
|
|
3112
|
+
return { c, t: clamp((x - xs[c]) / (xs[c + 1] - xs[c]), 0, 1) };
|
|
3113
|
+
}
|
|
3114
|
+
function rowWindow(ys, r, limit) {
|
|
3115
|
+
const n = ys.length;
|
|
3116
|
+
return [
|
|
3117
|
+
Math.max(0, Math.floor(ys[0] - ys[Math.max(0, r - 1)])),
|
|
3118
|
+
Math.min(limit, Math.ceil(ys[0] - ys[Math.min(n - 1, r + 1)]))
|
|
3119
|
+
];
|
|
3120
|
+
}
|
|
3068
3121
|
function renderedCrownBlend(m, ys) {
|
|
3069
3122
|
const wr = ys.map((y) => crownBlend(m, y));
|
|
3070
3123
|
return {
|
|
@@ -3140,10 +3193,11 @@ function paints(runs, px) {
|
|
|
3140
3193
|
}
|
|
3141
3194
|
return false;
|
|
3142
3195
|
}
|
|
3143
|
-
function outerRide(m, inner, coreAt, followAt) {
|
|
3196
|
+
function outerRide(m, inner, coreAt, followAt, capLimit) {
|
|
3144
3197
|
const f = m.frame;
|
|
3145
|
-
const
|
|
3146
|
-
if (
|
|
3198
|
+
const grid = m.hairFrontGrid;
|
|
3199
|
+
if (grid === void 0) return void 0;
|
|
3200
|
+
const { xs, ys } = grid;
|
|
3147
3201
|
const top = ys[0];
|
|
3148
3202
|
if (f.hairRuns("hair_front", top) === void 0 || f.hairRuns("hair_back", top) === void 0) {
|
|
3149
3203
|
return void 0;
|
|
@@ -3152,6 +3206,9 @@ function outerRide(m, inner, coreAt, followAt) {
|
|
|
3152
3206
|
const sides = [-1, 1];
|
|
3153
3207
|
const n = ys.length;
|
|
3154
3208
|
const count = Math.round(top - ys[n - 1]);
|
|
3209
|
+
let last = 0;
|
|
3210
|
+
while (last + 1 < n && ys[last + 1] >= capLimit) last++;
|
|
3211
|
+
const capRows = last === 0 ? 0 : Math.min(count, Math.floor(top - ys[last] - 0.5) + 1);
|
|
3155
3212
|
const below = (NOD.hairFront.down - NOD.hairBack.down) * f.hh;
|
|
3156
3213
|
const above = (NOD.hairBack.up - NOD.hairFront.up) * f.hh;
|
|
3157
3214
|
const j0 = Math.floor(-above);
|
|
@@ -3160,8 +3217,9 @@ function outerRide(m, inner, coreAt, followAt) {
|
|
|
3160
3217
|
(_, j) => f.hairRuns("hair_back", top - (j + j0) - 0.5)
|
|
3161
3218
|
);
|
|
3162
3219
|
const offset = sides.map(() => new Float64Array(count).fill(NaN));
|
|
3163
|
-
const
|
|
3164
|
-
const
|
|
3220
|
+
const reachOut = sides.map(() => new Float64Array(count));
|
|
3221
|
+
const reachIn = sides.map(() => new Float64Array(count));
|
|
3222
|
+
const bound = sides.map(() => new Uint8Array(count));
|
|
3165
3223
|
for (let i = 0; i < count; i++) {
|
|
3166
3224
|
const y = top - i - 0.5;
|
|
3167
3225
|
const front = f.hairRuns("hair_front", y);
|
|
@@ -3176,24 +3234,25 @@ function outerRide(m, inner, coreAt, followAt) {
|
|
|
3176
3234
|
inward = Math.min(inward, c.inward);
|
|
3177
3235
|
}
|
|
3178
3236
|
offset[k][i] = side * (edge - f.axisX);
|
|
3179
|
-
|
|
3180
|
-
|
|
3237
|
+
reachOut[k][i] = outward;
|
|
3238
|
+
reachIn[k][i] = inward;
|
|
3239
|
+
if (i < capRows && runReach(backRows[i - j0], edge, side).inward > 0)
|
|
3240
|
+
bound[k][i] = 1;
|
|
3181
3241
|
});
|
|
3182
3242
|
}
|
|
3183
3243
|
const end = new Float64Array(n * 2);
|
|
3184
3244
|
const lift = new Float64Array(n * 4);
|
|
3185
3245
|
for (let r = 0; r < n; r++) {
|
|
3186
|
-
const i0 =
|
|
3187
|
-
const i1 = Math.min(count, Math.ceil(top - ys[Math.min(n - 1, r + 1)]));
|
|
3246
|
+
const [i0, i1] = rowWindow(ys, r, count);
|
|
3188
3247
|
sides.forEach((side, k) => {
|
|
3189
3248
|
let d = Infinity;
|
|
3190
|
-
let
|
|
3191
|
-
let
|
|
3249
|
+
let outward = Infinity;
|
|
3250
|
+
let inward = Infinity;
|
|
3192
3251
|
for (let i = i0; i < i1; i++) {
|
|
3193
3252
|
if (Number.isNaN(offset[k][i])) continue;
|
|
3194
3253
|
d = Math.min(d, offset[k][i]);
|
|
3195
|
-
|
|
3196
|
-
|
|
3254
|
+
outward = Math.min(outward, reachOut[k][i]);
|
|
3255
|
+
inward = Math.min(inward, reachIn[k][i]);
|
|
3197
3256
|
}
|
|
3198
3257
|
end[r * 2 + k] = d;
|
|
3199
3258
|
if (d === Infinity || d <= inner) return;
|
|
@@ -3201,7 +3260,7 @@ function outerRide(m, inner, coreAt, followAt) {
|
|
|
3201
3260
|
sides.forEach((s, j) => {
|
|
3202
3261
|
const allow = Math.min(
|
|
3203
3262
|
coreAt(x, ys[r], s),
|
|
3204
|
-
s === side ?
|
|
3263
|
+
m.shellScale * (s === side ? outward : inward) - drift
|
|
3205
3264
|
);
|
|
3206
3265
|
lift[(r * 2 + k) * 2 + j] = Math.max(0, allow - followAt(x, ys[r], s));
|
|
3207
3266
|
});
|
|
@@ -3211,7 +3270,7 @@ function outerRide(m, inner, coreAt, followAt) {
|
|
|
3211
3270
|
const v = lift[(r * 2 + k) * 2 + j];
|
|
3212
3271
|
return v === 0 ? 0 : v * Math.min(1, (d - inner) / (end[r * 2 + k] - inner));
|
|
3213
3272
|
};
|
|
3214
|
-
|
|
3273
|
+
const liftAt = (x, y, s) => {
|
|
3215
3274
|
const d = Math.abs(x - f.axisX);
|
|
3216
3275
|
if (s === 0 || d <= inner) return 0;
|
|
3217
3276
|
const k = x < f.axisX ? 0 : 1;
|
|
@@ -3219,6 +3278,130 @@ function outerRide(m, inner, coreAt, followAt) {
|
|
|
3219
3278
|
const { r, t } = gridRow(ys, y, true);
|
|
3220
3279
|
return rowRide(r, k, j, d) * (1 - t) + rowRide(r + 1, k, j, d) * t;
|
|
3221
3280
|
};
|
|
3281
|
+
const nc = xs.length;
|
|
3282
|
+
const along = (v, x) => {
|
|
3283
|
+
const { c, t } = gridCol(xs, x);
|
|
3284
|
+
return v[c] + (v[c + 1] - v[c]) * t;
|
|
3285
|
+
};
|
|
3286
|
+
const readAt = (r, x, y) => {
|
|
3287
|
+
const { c, t: fx } = gridCol(xs, x);
|
|
3288
|
+
const dx = xs[c + 1] - xs[c];
|
|
3289
|
+
if (r + 1 < n && y <= ys[r] && y >= ys[r + 1]) {
|
|
3290
|
+
const fy = (ys[r] - y) / (ys[r] - ys[r + 1]);
|
|
3291
|
+
if (fy >= 1) return void 0;
|
|
3292
|
+
return fx <= fy ? xs[c] : xs[c] + (fx - fy) / (1 - fy) * dx;
|
|
3293
|
+
}
|
|
3294
|
+
if (r > 0 && y > ys[r] && y < ys[r - 1]) {
|
|
3295
|
+
const fy = (ys[r - 1] - y) / (ys[r - 1] - ys[r]);
|
|
3296
|
+
return fx <= fy ? xs[c] + fx / fy * dx : xs[c + 1];
|
|
3297
|
+
}
|
|
3298
|
+
return void 0;
|
|
3299
|
+
};
|
|
3300
|
+
const ahead = (e, s) => s === e.side ? e.outward : e.inward;
|
|
3301
|
+
const behind = (e, s) => s === e.side ? e.inward : e.outward;
|
|
3302
|
+
const capCut = (r, s, side, today, mine) => {
|
|
3303
|
+
const reach = Math.max(...mine.map((e) => side * (e.x - f.axisX)));
|
|
3304
|
+
const quarter = new Float64Array(nc);
|
|
3305
|
+
const cols = xs.map((_, c) => c).filter((c) => (xs[c] < f.axisX ? -1 : 1) === side);
|
|
3306
|
+
if (side < 0) cols.reverse();
|
|
3307
|
+
let total = 0;
|
|
3308
|
+
let x0 = f.axisX;
|
|
3309
|
+
let d0 = s * followAt(f.axisX, ys[r], s);
|
|
3310
|
+
let open = true;
|
|
3311
|
+
for (const c of cols) {
|
|
3312
|
+
if (open) {
|
|
3313
|
+
const d = (m.shellScale - 1) * (xs[c] - f.axisX) + s * today[c];
|
|
3314
|
+
const rest = side * (xs[c] - x0);
|
|
3315
|
+
const full = rest + side * (d - d0);
|
|
3316
|
+
total += Math.max(0, Math.min(full, rest)) / 4;
|
|
3317
|
+
x0 = xs[c];
|
|
3318
|
+
d0 = d;
|
|
3319
|
+
open = side * (xs[c] - f.axisX) < reach;
|
|
3320
|
+
}
|
|
3321
|
+
quarter[c] = total;
|
|
3322
|
+
}
|
|
3323
|
+
let scale = 0;
|
|
3324
|
+
for (const e of mine) {
|
|
3325
|
+
const need = along(today, e.at) - (m.shellScale * ahead(e, s) - drift);
|
|
3326
|
+
if (need <= 0) continue;
|
|
3327
|
+
const q = along(quarter, e.at);
|
|
3328
|
+
scale = Math.max(scale, q > 0 ? need / q : 1);
|
|
3329
|
+
}
|
|
3330
|
+
scale = Math.min(1, scale);
|
|
3331
|
+
return quarter.map((q) => scale * q);
|
|
3332
|
+
};
|
|
3333
|
+
const cuts = new Float64Array(n * 2 * nc);
|
|
3334
|
+
const blend = new Float64Array(n * 2);
|
|
3335
|
+
const wr = ys.map((y) => crownBlend(m, y));
|
|
3336
|
+
for (let r = 0; r <= last && capRows > 0; r++) {
|
|
3337
|
+
const w = wr[r];
|
|
3338
|
+
if (w <= 0) continue;
|
|
3339
|
+
const edges = [];
|
|
3340
|
+
const [i0, i1] = rowWindow(ys, r, capRows);
|
|
3341
|
+
for (let i = i0; i < i1; i++) {
|
|
3342
|
+
sides.forEach((side, k) => {
|
|
3343
|
+
if (bound[k][i] === 0) return;
|
|
3344
|
+
const x = f.axisX + side * offset[k][i];
|
|
3345
|
+
const at = readAt(r, x, top - i - 0.5);
|
|
3346
|
+
if (at === void 0) return;
|
|
3347
|
+
edges.push({
|
|
3348
|
+
side,
|
|
3349
|
+
x,
|
|
3350
|
+
at,
|
|
3351
|
+
outward: reachOut[k][i],
|
|
3352
|
+
inward: reachIn[k][i]
|
|
3353
|
+
});
|
|
3354
|
+
});
|
|
3355
|
+
}
|
|
3356
|
+
if (edges.length === 0) continue;
|
|
3357
|
+
sides.forEach((s, j) => {
|
|
3358
|
+
const today = xs.map((x) => followAt(x, ys[r], s) + liftAt(x, ys[r], s));
|
|
3359
|
+
const after = today.slice();
|
|
3360
|
+
if (w >= 1) {
|
|
3361
|
+
for (const side of sides) {
|
|
3362
|
+
const mine = edges.filter((e) => e.side === side);
|
|
3363
|
+
if (mine.length === 0) continue;
|
|
3364
|
+
const cut = capCut(r, s, side, today, mine);
|
|
3365
|
+
for (let c = 0; c < nc; c++) after[c] -= cut[c];
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
let b = 0;
|
|
3369
|
+
for (const e of edges) {
|
|
3370
|
+
const rel = w * (along(after, e.at) + drift);
|
|
3371
|
+
const hi = m.shellScale * ahead(e, s);
|
|
3372
|
+
const lo = -m.shellScale * behind(e, s);
|
|
3373
|
+
if (rel > hi) b = Math.max(b, 1 - hi / rel);
|
|
3374
|
+
else if (rel < lo) b = Math.max(b, 1 - lo / rel);
|
|
3375
|
+
}
|
|
3376
|
+
blend[r * 2 + j] = b;
|
|
3377
|
+
for (let c = 0; c < nc; c++)
|
|
3378
|
+
cuts[(r * 2 + j) * nc + c] = after[c] - today[c];
|
|
3379
|
+
});
|
|
3380
|
+
}
|
|
3381
|
+
for (let j = 0; j < 2; j++) {
|
|
3382
|
+
for (let r = n - 2; r >= 0; r--) {
|
|
3383
|
+
if (wr[r] < 1)
|
|
3384
|
+
blend[r * 2 + j] = Math.max(blend[r * 2 + j], blend[(r + 1) * 2 + j]);
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
const cutRow = (r, j, x) => r > last ? 0 : along(cuts.subarray((r * 2 + j) * nc, (r * 2 + j + 1) * nc), x);
|
|
3388
|
+
return {
|
|
3389
|
+
lift: liftAt,
|
|
3390
|
+
// Between vertex rows, as the render interpolates; a vertex reads its own
|
|
3391
|
+
// row's alone.
|
|
3392
|
+
cut: (x, y, s) => {
|
|
3393
|
+
if (s === 0) return 0;
|
|
3394
|
+
const j = s < 0 ? 0 : 1;
|
|
3395
|
+
const { r, t } = gridRow(ys, y, true);
|
|
3396
|
+
return cutRow(r, j, x) * (1 - t) + cutRow(r + 1, j, x) * t;
|
|
3397
|
+
},
|
|
3398
|
+
blend: (y, s) => {
|
|
3399
|
+
if (s === 0) return 0;
|
|
3400
|
+
const j = s < 0 ? 0 : 1;
|
|
3401
|
+
const { r, t } = gridRow(ys, y, true);
|
|
3402
|
+
return blend[r * 2 + j] * (1 - t) + blend[(r + 1) * 2 + j] * t;
|
|
3403
|
+
}
|
|
3404
|
+
};
|
|
3222
3405
|
}
|
|
3223
3406
|
function crownRide(m, cap, relAt) {
|
|
3224
3407
|
const f = m.frame;
|
|
@@ -3258,8 +3441,7 @@ function crownRide(m, cap, relAt) {
|
|
|
3258
3441
|
return rel[at];
|
|
3259
3442
|
};
|
|
3260
3443
|
const cellR = (x, y, k) => {
|
|
3261
|
-
|
|
3262
|
-
while (c < xs.length - 2 && xs[c + 1] < x) c++;
|
|
3444
|
+
const { c } = gridCol(xs, x);
|
|
3263
3445
|
const { r } = gridRow(ys, y);
|
|
3264
3446
|
return Math.max(
|
|
3265
3447
|
relOf(r, c, k),
|
|
@@ -3349,8 +3531,7 @@ function crownRide(m, cap, relAt) {
|
|
|
3349
3531
|
const out = wr.slice();
|
|
3350
3532
|
for (let r = 0; r < n; r++) {
|
|
3351
3533
|
if (wr[r] >= 1) continue;
|
|
3352
|
-
const i0 =
|
|
3353
|
-
const i1 = Math.min(count, Math.ceil(top - ys[Math.min(n - 1, r + 1)]));
|
|
3534
|
+
const [i0, i1] = rowWindow(ys, r, count);
|
|
3354
3535
|
let least = Infinity;
|
|
3355
3536
|
for (let i = i0; i < i1; i++) least = Math.min(least, slackAt(i, k));
|
|
3356
3537
|
out[r] = wr[r] + clamp(least, 0, 1 - wr[r]);
|
|
@@ -4019,6 +4200,18 @@ function fitTurn(ctx, targets, style, build) {
|
|
|
4019
4200
|
}
|
|
4020
4201
|
p.shell = bisect((k) => at(k) - s, SHELL_RANGE[0], SHELL_RANGE[1]);
|
|
4021
4202
|
}
|
|
4203
|
+
const covered = (shell) => farEarCovered(build({ ...p, shell }));
|
|
4204
|
+
if (!covered(p.shell) && covered(Math.max(1, p.shell))) {
|
|
4205
|
+
let lo = p.shell;
|
|
4206
|
+
let hi = Math.max(1, p.shell);
|
|
4207
|
+
for (let i = 0; i < 50; i++) {
|
|
4208
|
+
const mid = (lo + hi) / 2;
|
|
4209
|
+
if (covered(mid)) hi = mid;
|
|
4210
|
+
else lo = mid;
|
|
4211
|
+
}
|
|
4212
|
+
p.shell = hi;
|
|
4213
|
+
clamped.push("silhouetteRatio");
|
|
4214
|
+
}
|
|
4022
4215
|
const hb = ctx.restHalf;
|
|
4023
4216
|
const feature = (key, family, at, box) => {
|
|
4024
4217
|
const base = build(p);
|
|
@@ -4177,6 +4370,7 @@ function buildTurn(frame, landmarks, q) {
|
|
|
4177
4370
|
hairOuter: q.outline * face,
|
|
4178
4371
|
hairBack: A * hh * TURN.hairBack,
|
|
4179
4372
|
shellScale: q.shell,
|
|
4373
|
+
earFarScale: 1 + A * (TURN.earFarScale - 1),
|
|
4180
4374
|
...landmarks
|
|
4181
4375
|
};
|
|
4182
4376
|
}
|