@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.js
CHANGED
|
@@ -2433,7 +2433,8 @@ var TURN = {
|
|
|
2433
2433
|
/** The back hair's slight counter-motion. */
|
|
2434
2434
|
hairBack: -0.027,
|
|
2435
2435
|
/** The ears lag the plate: the far one moves this share of its slide, the
|
|
2436
|
-
* near one this
|
|
2436
|
+
* near one this at its widest reach, its root riding the head (the
|
|
2437
|
+
* samples' parallax ratios, 0.44 and 0.87). */
|
|
2437
2438
|
earFar: 0.44,
|
|
2438
2439
|
earNear: 0.87
|
|
2439
2440
|
};
|
|
@@ -2531,14 +2532,28 @@ function faceField(m) {
|
|
|
2531
2532
|
};
|
|
2532
2533
|
}
|
|
2533
2534
|
function earField(m) {
|
|
2535
|
+
const f = m.frame;
|
|
2536
|
+
const ears = f.ears;
|
|
2537
|
+
if (ears === void 0) return () => [0, 0];
|
|
2534
2538
|
const face = faceField(m);
|
|
2535
2539
|
return (x, y, ax, ay) => {
|
|
2536
2540
|
const s = Math.sign(ax);
|
|
2537
2541
|
const a = Math.abs(ax) / 30;
|
|
2538
|
-
const
|
|
2539
|
-
return [s * a *
|
|
2542
|
+
const dy = face(x, y, 0, ay)[1];
|
|
2543
|
+
if (sideOf(m, x, s) === "far") return [s * a * TURN.earFar * m.face, dy];
|
|
2544
|
+
const d = Math.abs(x - f.axisX);
|
|
2545
|
+
const attach = ears.attachAt(y);
|
|
2546
|
+
if (d <= attach) return [face(x, y, ax, 0)[0], dy];
|
|
2547
|
+
const root = face(f.axisX + Math.sign(x - f.axisX) * attach, y, ax, 0)[0];
|
|
2548
|
+
const rim = s * a * TURN.earNear * m.face;
|
|
2549
|
+
const t = Math.min(1, (d - attach) / Math.max(1, ears.outer - attach));
|
|
2550
|
+
return [root + (rim - root) * t, dy];
|
|
2540
2551
|
};
|
|
2541
2552
|
}
|
|
2553
|
+
function chinSlide(m) {
|
|
2554
|
+
const f = m.frame;
|
|
2555
|
+
return Math.abs(faceField(m)(f.axisX, f.chinY, 30, 0)[0]);
|
|
2556
|
+
}
|
|
2542
2557
|
var NECK_SHADE_MAX = 0.45;
|
|
2543
2558
|
var NECK_SHADE_FULL = 0.2;
|
|
2544
2559
|
function neckField(m) {
|
|
@@ -2546,7 +2561,7 @@ function neckField(m) {
|
|
|
2546
2561
|
const nk = f.neck;
|
|
2547
2562
|
if (nk === void 0) return () => [0, 0];
|
|
2548
2563
|
const face = faceField(m);
|
|
2549
|
-
const full =
|
|
2564
|
+
const full = chinSlide(m);
|
|
2550
2565
|
const share = Math.min(1, NECK_SHADE_MAX * nk.waist / Math.max(1e-6, full));
|
|
2551
2566
|
const bottom = f.face.y0;
|
|
2552
2567
|
const tip = f.cutAt(f.axisX);
|
|
@@ -2885,7 +2900,8 @@ function boxSmooth(v, k) {
|
|
|
2885
2900
|
});
|
|
2886
2901
|
}
|
|
2887
2902
|
var CUT_MARGIN = 2;
|
|
2888
|
-
|
|
2903
|
+
var CUT_LOW = 3;
|
|
2904
|
+
function fadeToOutline(d, nk) {
|
|
2889
2905
|
return Math.max(0, 1 - d / nk.waist);
|
|
2890
2906
|
}
|
|
2891
2907
|
var STROKE_RUN = 4;
|
|
@@ -3043,20 +3059,30 @@ function buildHeadFrame(layers, opts) {
|
|
|
3043
3059
|
};
|
|
3044
3060
|
const chin = chinY;
|
|
3045
3061
|
const shade = JAW_SHADE * HH_PER_EYE_TO_CHIN * Math.max(1, eyeY - chin);
|
|
3062
|
+
const reach = opts.chinSlide === void 0 ? nk.waist : clamp(
|
|
3063
|
+
nk.waist - opts.chinSlide - CUT_MARGIN - (CUT_LOW + 1),
|
|
3064
|
+
0,
|
|
3065
|
+
nk.waist
|
|
3066
|
+
);
|
|
3067
|
+
let banded = -1;
|
|
3046
3068
|
const cut = new Float64Array(faceLayer.cropW);
|
|
3047
3069
|
for (let i = 0; i < faceLayer.cropW; i++) {
|
|
3048
3070
|
const d = Math.abs(colX(i) - axisX);
|
|
3049
3071
|
const stroke = d <= nk.half ? strokeAt(i) : void 0;
|
|
3050
|
-
const
|
|
3072
|
+
const band = reach > 0 ? Math.max(0, 1 - d / reach) : 0;
|
|
3073
|
+
if (stroke !== void 0 && band > 0) banded = Math.max(banded, d);
|
|
3074
|
+
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;
|
|
3051
3075
|
cut[i] = y >= face.y1 ? face.y1 : y;
|
|
3052
3076
|
}
|
|
3053
3077
|
const low = cut.map((_, i) => {
|
|
3054
3078
|
let m = Infinity;
|
|
3055
|
-
for (let j = i -
|
|
3079
|
+
for (let j = i - CUT_LOW; j <= i + CUT_LOW; j++) {
|
|
3056
3080
|
if (j >= 0 && j < cut.length) m = Math.min(m, cut[j]);
|
|
3057
3081
|
}
|
|
3058
3082
|
return m;
|
|
3059
3083
|
});
|
|
3084
|
+
if (opts.chinSlide !== void 0 && banded >= 0)
|
|
3085
|
+
nk.bandEnd = banded + CUT_LOW + 1;
|
|
3060
3086
|
cutAt = (x) => {
|
|
3061
3087
|
const f = x - (faceLayer.bbox.x - canvasW / 2) - 0.5;
|
|
3062
3088
|
const i = clamp(Math.floor(f), 0, low.length - 1);
|
|
@@ -3193,12 +3219,20 @@ function buildFaceMesh(frame, b, head, neckCells) {
|
|
|
3193
3219
|
frame.axisX - neck2.waist,
|
|
3194
3220
|
frame.axisX + neck2.waist
|
|
3195
3221
|
);
|
|
3222
|
+
if (neck2.bandEnd !== void 0)
|
|
3223
|
+
at.push(frame.axisX - neck2.bandEnd, frame.axisX + neck2.bandEnd);
|
|
3196
3224
|
}
|
|
3225
|
+
const placed = /* @__PURE__ */ new Set();
|
|
3197
3226
|
for (const k of at) {
|
|
3198
3227
|
if (k <= x0 || k >= x1) continue;
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3228
|
+
if ([...placed].some((i) => Math.abs(xs2[i] - k) < 0.5)) continue;
|
|
3229
|
+
const near = xs2.findIndex(
|
|
3230
|
+
(x, i) => !placed.has(i) && Math.abs(x - k) < 0.3 * step
|
|
3231
|
+
);
|
|
3232
|
+
if (near > 0 && near < n) {
|
|
3233
|
+
xs2[near] = k;
|
|
3234
|
+
placed.add(near);
|
|
3235
|
+
} else if (near < 0) placed.add(xs2.push(k) - 1);
|
|
3202
3236
|
}
|
|
3203
3237
|
return xs2.sort((a, b2) => a - b2);
|
|
3204
3238
|
};
|
|
@@ -3214,7 +3248,7 @@ function buildFaceMesh(frame, b, head, neckCells) {
|
|
|
3214
3248
|
);
|
|
3215
3249
|
const sampleBelow = (x) => Math.max(
|
|
3216
3250
|
1,
|
|
3217
|
-
SAMPLE_BELOW * frame.hh *
|
|
3251
|
+
SAMPLE_BELOW * frame.hh * fadeToOutline(Math.abs(x - frame.axisX), neck)
|
|
3218
3252
|
);
|
|
3219
3253
|
let highest = -Infinity;
|
|
3220
3254
|
for (const x of xs2) {
|
|
@@ -3666,8 +3700,7 @@ function eyesInPlate(ctx, m) {
|
|
|
3666
3700
|
function chinOverNeck(ctx, m) {
|
|
3667
3701
|
const { frame } = ctx;
|
|
3668
3702
|
if (frame.neck === void 0) return true;
|
|
3669
|
-
|
|
3670
|
-
return Math.abs(f(frame.axisX, frame.chinY, 30, 0)[0]) <= frame.neck.half;
|
|
3703
|
+
return chinSlide(m) <= frame.neck.half;
|
|
3671
3704
|
}
|
|
3672
3705
|
function strandHeld(ctx, m) {
|
|
3673
3706
|
const land2 = analyticLander(m);
|
|
@@ -3982,10 +4015,28 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
3982
4015
|
checkedKeys("turnTargets", options.turnTargets, TURN_TARGET_KEYS);
|
|
3983
4016
|
const targets = hasNose ? resolveTurnTargets(options.turnTargets, plateHalf) : { given: /* @__PURE__ */ new Set() };
|
|
3984
4017
|
const turnOptions = hasNose ? options : {};
|
|
3985
|
-
const
|
|
4018
|
+
const frameOptions = {
|
|
3986
4019
|
headHalfWidth: targets.headHalfWidth,
|
|
3987
4020
|
headEdges: targets.headHalfWidth !== void 0 ? options.headEdges : void 0
|
|
4021
|
+
};
|
|
4022
|
+
const solveFrame = buildHeadFrame(layers, frameOptions);
|
|
4023
|
+
const ctx = solveContext(solveFrame, byRole, turnOptions, hasNose);
|
|
4024
|
+
const landmarks = {
|
|
4025
|
+
boxes: Object.fromEntries(
|
|
4026
|
+
["eye_L", "eye_R", "brow_L", "brow_R"].filter(has).map((r) => [r, box(r)])
|
|
4027
|
+
),
|
|
4028
|
+
noseAt: ctx.noseAt,
|
|
4029
|
+
noseTopY: ctx.noseBox?.y1,
|
|
4030
|
+
mouthAt: ctx.mouthAt,
|
|
4031
|
+
hairTop: has("hair_front") ? box("hair_front").y1 : solveFrame.face.y1
|
|
4032
|
+
};
|
|
4033
|
+
const build = (q) => buildTurn(solveFrame, landmarks, q);
|
|
4034
|
+
const fit = fitTurn(ctx, targets, style, build);
|
|
4035
|
+
const frame = buildHeadFrame(layers, {
|
|
4036
|
+
...frameOptions,
|
|
4037
|
+
chinSlide: chinSlide(fit.model)
|
|
3988
4038
|
});
|
|
4039
|
+
const turn = { ...fit.model, frame };
|
|
3989
4040
|
const parts = [];
|
|
3990
4041
|
const reach = /* @__PURE__ */ new Map();
|
|
3991
4042
|
let regionOf = [];
|
|
@@ -4008,19 +4059,6 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
4008
4059
|
reach.set(spec.role, built.reach);
|
|
4009
4060
|
parts.push(built.part);
|
|
4010
4061
|
}
|
|
4011
|
-
const ctx = solveContext(frame, byRole, turnOptions, hasNose);
|
|
4012
|
-
const landmarks = {
|
|
4013
|
-
boxes: Object.fromEntries(
|
|
4014
|
-
["eye_L", "eye_R", "brow_L", "brow_R"].filter(has).map((r) => [r, box(r)])
|
|
4015
|
-
),
|
|
4016
|
-
noseAt: ctx.noseAt,
|
|
4017
|
-
noseTopY: ctx.noseBox?.y1,
|
|
4018
|
-
mouthAt: ctx.mouthAt,
|
|
4019
|
-
hairTop: has("hair_front") ? box("hair_front").y1 : frame.face.y1
|
|
4020
|
-
};
|
|
4021
|
-
const build = (q) => buildTurn(frame, landmarks, q);
|
|
4022
|
-
const fit = fitTurn(ctx, targets, style, build);
|
|
4023
|
-
const turn = fit.model;
|
|
4024
4062
|
const grids = /* @__PURE__ */ new Map();
|
|
4025
4063
|
for (const family of Object.keys(WARP_ID)) {
|
|
4026
4064
|
const members = parts.filter((p) => roleSpec(p.id).family === family);
|