@ikijs/editor 0.10.0 → 0.10.1
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/dist/index.js +65 -27
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +65 -27
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2364,7 +2364,8 @@ var TURN = {
|
|
|
2364
2364
|
/** The back hair's slight counter-motion. */
|
|
2365
2365
|
hairBack: -0.027,
|
|
2366
2366
|
/** The ears lag the plate: the far one moves this share of its slide, the
|
|
2367
|
-
* near one this
|
|
2367
|
+
* near one this at its widest reach, its root riding the head (the
|
|
2368
|
+
* samples' parallax ratios, 0.44 and 0.87). */
|
|
2368
2369
|
earFar: 0.44,
|
|
2369
2370
|
earNear: 0.87
|
|
2370
2371
|
};
|
|
@@ -2462,14 +2463,28 @@ function faceField(m) {
|
|
|
2462
2463
|
};
|
|
2463
2464
|
}
|
|
2464
2465
|
function earField(m) {
|
|
2466
|
+
const f = m.frame;
|
|
2467
|
+
const ears = f.ears;
|
|
2468
|
+
if (ears === void 0) return () => [0, 0];
|
|
2465
2469
|
const face = faceField(m);
|
|
2466
2470
|
return (x, y, ax, ay) => {
|
|
2467
2471
|
const s = Math.sign(ax);
|
|
2468
2472
|
const a = Math.abs(ax) / 30;
|
|
2469
|
-
const
|
|
2470
|
-
return [s * a *
|
|
2473
|
+
const dy = face(x, y, 0, ay)[1];
|
|
2474
|
+
if (sideOf(m, x, s) === "far") return [s * a * TURN.earFar * m.face, dy];
|
|
2475
|
+
const d = Math.abs(x - f.axisX);
|
|
2476
|
+
const attach = ears.attachAt(y);
|
|
2477
|
+
if (d <= attach) return [face(x, y, ax, 0)[0], dy];
|
|
2478
|
+
const root = face(f.axisX + Math.sign(x - f.axisX) * attach, y, ax, 0)[0];
|
|
2479
|
+
const rim = s * a * TURN.earNear * m.face;
|
|
2480
|
+
const t = Math.min(1, (d - attach) / Math.max(1, ears.outer - attach));
|
|
2481
|
+
return [root + (rim - root) * t, dy];
|
|
2471
2482
|
};
|
|
2472
2483
|
}
|
|
2484
|
+
function chinSlide(m) {
|
|
2485
|
+
const f = m.frame;
|
|
2486
|
+
return Math.abs(faceField(m)(f.axisX, f.chinY, 30, 0)[0]);
|
|
2487
|
+
}
|
|
2473
2488
|
var NECK_SHADE_MAX = 0.45;
|
|
2474
2489
|
var NECK_SHADE_FULL = 0.2;
|
|
2475
2490
|
function neckField(m) {
|
|
@@ -2477,7 +2492,7 @@ function neckField(m) {
|
|
|
2477
2492
|
const nk = f.neck;
|
|
2478
2493
|
if (nk === void 0) return () => [0, 0];
|
|
2479
2494
|
const face = faceField(m);
|
|
2480
|
-
const full =
|
|
2495
|
+
const full = chinSlide(m);
|
|
2481
2496
|
const share = Math.min(1, NECK_SHADE_MAX * nk.waist / Math.max(1e-6, full));
|
|
2482
2497
|
const bottom = f.face.y0;
|
|
2483
2498
|
const tip = f.cutAt(f.axisX);
|
|
@@ -2816,7 +2831,8 @@ function boxSmooth(v, k) {
|
|
|
2816
2831
|
});
|
|
2817
2832
|
}
|
|
2818
2833
|
var CUT_MARGIN = 2;
|
|
2819
|
-
|
|
2834
|
+
var CUT_LOW = 3;
|
|
2835
|
+
function fadeToOutline(d, nk) {
|
|
2820
2836
|
return Math.max(0, 1 - d / nk.waist);
|
|
2821
2837
|
}
|
|
2822
2838
|
var STROKE_RUN = 4;
|
|
@@ -2974,20 +2990,30 @@ function buildHeadFrame(layers, opts) {
|
|
|
2974
2990
|
};
|
|
2975
2991
|
const chin = chinY;
|
|
2976
2992
|
const shade = JAW_SHADE * HH_PER_EYE_TO_CHIN * Math.max(1, eyeY - chin);
|
|
2993
|
+
const reach = opts.chinSlide === void 0 ? nk.waist : clamp(
|
|
2994
|
+
nk.waist - opts.chinSlide - CUT_MARGIN - (CUT_LOW + 1),
|
|
2995
|
+
0,
|
|
2996
|
+
nk.waist
|
|
2997
|
+
);
|
|
2998
|
+
let banded = -1;
|
|
2977
2999
|
const cut = new Float64Array(faceLayer.cropW);
|
|
2978
3000
|
for (let i = 0; i < faceLayer.cropW; i++) {
|
|
2979
3001
|
const d = Math.abs(colX(i) - axisX);
|
|
2980
3002
|
const stroke = d <= nk.half ? strokeAt(i) : void 0;
|
|
2981
|
-
const
|
|
3003
|
+
const band = reach > 0 ? Math.max(0, 1 - d / reach) : 0;
|
|
3004
|
+
if (stroke !== void 0 && band > 0) banded = Math.max(banded, d);
|
|
3005
|
+
const y = stroke !== void 0 ? stroke - shade * band - CUT_MARGIN * fadeToOutline(d, nk) : (d <= nk.half ? chin + (nk.topY - chin) * (d / Math.max(1, nk.half)) : outlineAt(d)) - CUT_MARGIN;
|
|
2982
3006
|
cut[i] = y >= face.y1 ? face.y1 : y;
|
|
2983
3007
|
}
|
|
2984
3008
|
const low = cut.map((_, i) => {
|
|
2985
3009
|
let m = Infinity;
|
|
2986
|
-
for (let j = i -
|
|
3010
|
+
for (let j = i - CUT_LOW; j <= i + CUT_LOW; j++) {
|
|
2987
3011
|
if (j >= 0 && j < cut.length) m = Math.min(m, cut[j]);
|
|
2988
3012
|
}
|
|
2989
3013
|
return m;
|
|
2990
3014
|
});
|
|
3015
|
+
if (opts.chinSlide !== void 0 && banded >= 0)
|
|
3016
|
+
nk.bandEnd = banded + CUT_LOW + 1;
|
|
2991
3017
|
cutAt = (x) => {
|
|
2992
3018
|
const f = x - (faceLayer.bbox.x - canvasW / 2) - 0.5;
|
|
2993
3019
|
const i = clamp(Math.floor(f), 0, low.length - 1);
|
|
@@ -3124,12 +3150,20 @@ function buildFaceMesh(frame, b, head, neckCells) {
|
|
|
3124
3150
|
frame.axisX - neck2.waist,
|
|
3125
3151
|
frame.axisX + neck2.waist
|
|
3126
3152
|
);
|
|
3153
|
+
if (neck2.bandEnd !== void 0)
|
|
3154
|
+
at.push(frame.axisX - neck2.bandEnd, frame.axisX + neck2.bandEnd);
|
|
3127
3155
|
}
|
|
3156
|
+
const placed = /* @__PURE__ */ new Set();
|
|
3128
3157
|
for (const k of at) {
|
|
3129
3158
|
if (k <= x0 || k >= x1) continue;
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3159
|
+
if ([...placed].some((i) => Math.abs(xs2[i] - k) < 0.5)) continue;
|
|
3160
|
+
const near = xs2.findIndex(
|
|
3161
|
+
(x, i) => !placed.has(i) && Math.abs(x - k) < 0.3 * step
|
|
3162
|
+
);
|
|
3163
|
+
if (near > 0 && near < n) {
|
|
3164
|
+
xs2[near] = k;
|
|
3165
|
+
placed.add(near);
|
|
3166
|
+
} else if (near < 0) placed.add(xs2.push(k) - 1);
|
|
3133
3167
|
}
|
|
3134
3168
|
return xs2.sort((a, b2) => a - b2);
|
|
3135
3169
|
};
|
|
@@ -3145,7 +3179,7 @@ function buildFaceMesh(frame, b, head, neckCells) {
|
|
|
3145
3179
|
);
|
|
3146
3180
|
const sampleBelow = (x) => Math.max(
|
|
3147
3181
|
1,
|
|
3148
|
-
SAMPLE_BELOW * frame.hh *
|
|
3182
|
+
SAMPLE_BELOW * frame.hh * fadeToOutline(Math.abs(x - frame.axisX), neck)
|
|
3149
3183
|
);
|
|
3150
3184
|
let highest = -Infinity;
|
|
3151
3185
|
for (const x of xs2) {
|
|
@@ -3597,8 +3631,7 @@ function eyesInPlate(ctx, m) {
|
|
|
3597
3631
|
function chinOverNeck(ctx, m) {
|
|
3598
3632
|
const { frame } = ctx;
|
|
3599
3633
|
if (frame.neck === void 0) return true;
|
|
3600
|
-
|
|
3601
|
-
return Math.abs(f(frame.axisX, frame.chinY, 30, 0)[0]) <= frame.neck.half;
|
|
3634
|
+
return chinSlide(m) <= frame.neck.half;
|
|
3602
3635
|
}
|
|
3603
3636
|
function strandHeld(ctx, m) {
|
|
3604
3637
|
const land2 = analyticLander(m);
|
|
@@ -3915,10 +3948,28 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
3915
3948
|
checkedKeys("turnTargets", options.turnTargets, TURN_TARGET_KEYS);
|
|
3916
3949
|
const targets = hasNose ? resolveTurnTargets(options.turnTargets, plateHalf) : { given: /* @__PURE__ */ new Set() };
|
|
3917
3950
|
const turnOptions = hasNose ? options : {};
|
|
3918
|
-
const
|
|
3951
|
+
const frameOptions = {
|
|
3919
3952
|
headHalfWidth: targets.headHalfWidth,
|
|
3920
3953
|
headEdges: targets.headHalfWidth !== void 0 ? options.headEdges : void 0
|
|
3954
|
+
};
|
|
3955
|
+
const solveFrame = buildHeadFrame(layers, frameOptions);
|
|
3956
|
+
const ctx = solveContext(solveFrame, byRole, turnOptions, hasNose);
|
|
3957
|
+
const landmarks = {
|
|
3958
|
+
boxes: Object.fromEntries(
|
|
3959
|
+
["eye_L", "eye_R", "brow_L", "brow_R"].filter(has).map((r) => [r, box(r)])
|
|
3960
|
+
),
|
|
3961
|
+
noseAt: ctx.noseAt,
|
|
3962
|
+
noseTopY: ctx.noseBox?.y1,
|
|
3963
|
+
mouthAt: ctx.mouthAt,
|
|
3964
|
+
hairTop: has("hair_front") ? box("hair_front").y1 : solveFrame.face.y1
|
|
3965
|
+
};
|
|
3966
|
+
const build = (q) => buildTurn(solveFrame, landmarks, q);
|
|
3967
|
+
const fit = fitTurn(ctx, targets, style, build);
|
|
3968
|
+
const frame = buildHeadFrame(layers, {
|
|
3969
|
+
...frameOptions,
|
|
3970
|
+
chinSlide: chinSlide(fit.model)
|
|
3921
3971
|
});
|
|
3972
|
+
const turn = { ...fit.model, frame };
|
|
3922
3973
|
const parts = [];
|
|
3923
3974
|
const reach = /* @__PURE__ */ new Map();
|
|
3924
3975
|
let regionOf = [];
|
|
@@ -3941,19 +3992,6 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
3941
3992
|
reach.set(spec.role, built.reach);
|
|
3942
3993
|
parts.push(built.part);
|
|
3943
3994
|
}
|
|
3944
|
-
const ctx = solveContext(frame, byRole, turnOptions, hasNose);
|
|
3945
|
-
const landmarks = {
|
|
3946
|
-
boxes: Object.fromEntries(
|
|
3947
|
-
["eye_L", "eye_R", "brow_L", "brow_R"].filter(has).map((r) => [r, box(r)])
|
|
3948
|
-
),
|
|
3949
|
-
noseAt: ctx.noseAt,
|
|
3950
|
-
noseTopY: ctx.noseBox?.y1,
|
|
3951
|
-
mouthAt: ctx.mouthAt,
|
|
3952
|
-
hairTop: has("hair_front") ? box("hair_front").y1 : frame.face.y1
|
|
3953
|
-
};
|
|
3954
|
-
const build = (q) => buildTurn(frame, landmarks, q);
|
|
3955
|
-
const fit = fitTurn(ctx, targets, style, build);
|
|
3956
|
-
const turn = fit.model;
|
|
3957
3995
|
const grids = /* @__PURE__ */ new Map();
|
|
3958
3996
|
for (const family of Object.keys(WARP_ID)) {
|
|
3959
3997
|
const members = parts.filter((p) => roleSpec(p.id).family === family);
|