@ikijs/editor 0.32.1 → 0.33.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/README.md +1 -1
- package/dist/index.d.mts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +128 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +128 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -97,7 +97,7 @@ the turn's amount, the eyes' foreshortening, the hair's width, a feature's own
|
|
|
97
97
|
shift. With none given the profile itself renders (`DEFAULT_TURN_TARGETS`
|
|
98
98
|
expresses it in the cues' units). `options.rigPlan` tunes a character from the
|
|
99
99
|
profile in three groups (`turn`, `hair`, `eyes`: the whole turn, the
|
|
100
|
-
features' lead, the front hair's follow and its outline's, the blink and the
|
|
100
|
+
features' lead, the far ear's follow, the front hair's follow and its outline's, the blink and the
|
|
101
101
|
hair sway), validated against `RIG_PLAN_FIELDS`. `options.patches` adds displacements to a generated rig's keyform
|
|
102
102
|
stops (`RigPatch`), replayed on every rig and reported through
|
|
103
103
|
`options.onPatchesApplied`; a refused op throws `PatchError`. `options.customParts` adds parts the
|
package/dist/index.d.mts
CHANGED
|
@@ -709,6 +709,9 @@ interface RigPlan {
|
|
|
709
709
|
/** How far the nose leads the plate (turn only — the nod keeps the
|
|
710
710
|
* profile's nose depth). */
|
|
711
711
|
noseLead?: number;
|
|
712
|
+
/** How far the far ear's outer edge follows the face (0: it holds and
|
|
713
|
+
* the head slides over it; 1: it rides with the face and stays whole). */
|
|
714
|
+
farEar?: number;
|
|
712
715
|
};
|
|
713
716
|
hair?: {
|
|
714
717
|
/** The front hair's share of the face's turn. */
|
package/dist/index.d.ts
CHANGED
|
@@ -709,6 +709,9 @@ interface RigPlan {
|
|
|
709
709
|
/** How far the nose leads the plate (turn only — the nod keeps the
|
|
710
710
|
* profile's nose depth). */
|
|
711
711
|
noseLead?: number;
|
|
712
|
+
/** How far the far ear's outer edge follows the face (0: it holds and
|
|
713
|
+
* the head slides over it; 1: it rides with the face and stays whole). */
|
|
714
|
+
farEar?: number;
|
|
712
715
|
};
|
|
713
716
|
hair?: {
|
|
714
717
|
/** The front hair's share of the face's turn. */
|
package/dist/index.js
CHANGED
|
@@ -3025,6 +3025,7 @@ function mouthAnchor(byRole) {
|
|
|
3025
3025
|
}
|
|
3026
3026
|
|
|
3027
3027
|
// src/auto-rig/head.ts
|
|
3028
|
+
var EAR_EDGE = 3;
|
|
3028
3029
|
function outerEdge(runs, axisX, side) {
|
|
3029
3030
|
if (runs.length === 0) return void 0;
|
|
3030
3031
|
const x = side > 0 ? runs[runs.length - 1] : runs[0];
|
|
@@ -3058,7 +3059,11 @@ function findEars(raw, wMax, firstRow, lastRow, minRows, maxRows) {
|
|
|
3058
3059
|
if (raw[t] - raw[t - EAR_ROWS] < step) continue;
|
|
3059
3060
|
for (let b = t + minRows; b <= Math.min(lastRow, t + maxRows); b++) {
|
|
3060
3061
|
if (raw[b - EAR_ROWS] - raw[b] >= step) {
|
|
3061
|
-
|
|
3062
|
+
let end = b;
|
|
3063
|
+
for (let k = b + 1; k <= Math.min(lastRow, b + minRows); k++) {
|
|
3064
|
+
if (raw[k - EAR_ROWS] - raw[k] >= step) end = k;
|
|
3065
|
+
}
|
|
3066
|
+
return { top: t - EAR_ROWS, bottom: end };
|
|
3062
3067
|
}
|
|
3063
3068
|
}
|
|
3064
3069
|
return void 0;
|
|
@@ -3136,6 +3141,7 @@ function buildHeadFrame(layers, opts) {
|
|
|
3136
3141
|
return rows[i] + (rows[j] - rows[i]) * clamp(r - i, 0, 1);
|
|
3137
3142
|
};
|
|
3138
3143
|
const rawRows = raw;
|
|
3144
|
+
let earRows;
|
|
3139
3145
|
const earless = (chin) => {
|
|
3140
3146
|
const rowOf = (y) => Math.round(canvasH / 2 - y - faceLayer.bbox.y - 0.5);
|
|
3141
3147
|
const span3 = Math.max(1, eyeY - chin);
|
|
@@ -3158,8 +3164,10 @@ function buildHeadFrame(layers, opts) {
|
|
|
3158
3164
|
top: rowY(top) + 0.5,
|
|
3159
3165
|
bottom: rowY(bottom) - 0.5,
|
|
3160
3166
|
attachAt: (y) => attach(clamp(canvasH / 2 - y - faceLayer.bbox.y - 0.5, top, bottom)),
|
|
3161
|
-
outer
|
|
3167
|
+
outer,
|
|
3168
|
+
edgeAt: (y) => attach(clamp(canvasH / 2 - y - faceLayer.bbox.y - 0.5, top, bottom)) + EAR_EDGE
|
|
3162
3169
|
};
|
|
3170
|
+
earRows = { top, bottom };
|
|
3163
3171
|
return smoothProfile(
|
|
3164
3172
|
rawRows.map((v, r) => r > top && r < bottom ? attach(r) : v)
|
|
3165
3173
|
);
|
|
@@ -3281,9 +3289,13 @@ function buildHeadFrame(layers, opts) {
|
|
|
3281
3289
|
headHalfAt = readRows(head);
|
|
3282
3290
|
headMax = Math.max(...head);
|
|
3283
3291
|
if (ears !== void 0) {
|
|
3292
|
+
const band = earRows;
|
|
3293
|
+
const edge = head.map(
|
|
3294
|
+
(w, r) => r >= band.top && r <= band.bottom ? w + EAR_EDGE : w
|
|
3295
|
+
);
|
|
3284
3296
|
const bottomOf = (d) => {
|
|
3285
|
-
for (let r =
|
|
3286
|
-
if (
|
|
3297
|
+
for (let r = edge.length - 1; r >= 0; r--) {
|
|
3298
|
+
if (edge[r] >= d) return rowY(r) - 0.5;
|
|
3287
3299
|
}
|
|
3288
3300
|
return face.y1;
|
|
3289
3301
|
};
|
|
@@ -3451,6 +3463,19 @@ function spin(x, y, ox, oy, a) {
|
|
|
3451
3463
|
];
|
|
3452
3464
|
}
|
|
3453
3465
|
function faceField(m) {
|
|
3466
|
+
const plate = plateField(m);
|
|
3467
|
+
const floor = foreheadFloor(m);
|
|
3468
|
+
let hold;
|
|
3469
|
+
return (x, y, ax, ay) => {
|
|
3470
|
+
const d = plate(x, y, ax, ay);
|
|
3471
|
+
if (ax === 0 || y <= floor) return d;
|
|
3472
|
+
if (hold === void 0) hold = foreheadHold(m, floor) ?? null;
|
|
3473
|
+
if (hold === null) return d;
|
|
3474
|
+
const s = Math.sign(ax);
|
|
3475
|
+
return [d[0] - s * Math.abs(ax) * hold(x, y, s) / 30, d[1]];
|
|
3476
|
+
};
|
|
3477
|
+
}
|
|
3478
|
+
function plateField(m) {
|
|
3454
3479
|
const f = m.frame;
|
|
3455
3480
|
return (x, y, ax, ay) => {
|
|
3456
3481
|
const s = Math.sign(ax);
|
|
@@ -3470,6 +3495,77 @@ function faceField(m) {
|
|
|
3470
3495
|
return [dx, nodDy(v, ay, f.hh)];
|
|
3471
3496
|
};
|
|
3472
3497
|
}
|
|
3498
|
+
function faceCellPx(frame) {
|
|
3499
|
+
return Math.min(FACE_CELL_PX, 2 * frame.wMax / 14);
|
|
3500
|
+
}
|
|
3501
|
+
var FACE_CELL_PX = 28;
|
|
3502
|
+
var FOREHEAD_FADE = 0.04;
|
|
3503
|
+
function foreheadFloor(m) {
|
|
3504
|
+
const f = m.frame;
|
|
3505
|
+
const eyes = [m.boxes.eye_L, m.boxes.eye_R].filter(
|
|
3506
|
+
(e) => e !== void 0
|
|
3507
|
+
);
|
|
3508
|
+
const eyeTop = eyes.length > 0 ? Math.max(...eyes.map((e) => e.y1)) : f.eyeY;
|
|
3509
|
+
return Math.max(eyeTop, f.ears === void 0 ? -Infinity : f.ears.top + 2);
|
|
3510
|
+
}
|
|
3511
|
+
function foreheadHold(m, floor) {
|
|
3512
|
+
const f = m.frame;
|
|
3513
|
+
const top = f.face.y1;
|
|
3514
|
+
const count = Math.floor(top - floor);
|
|
3515
|
+
if (count <= 0 || f.faceRuns(top - 0.5) === void 0 || f.hairRuns("hair_front", top - 0.5) === void 0) {
|
|
3516
|
+
return void 0;
|
|
3517
|
+
}
|
|
3518
|
+
const plate = plateField(m);
|
|
3519
|
+
const front = hairFrontField(m);
|
|
3520
|
+
const { inner } = hairFrontBends(f, m.boxes);
|
|
3521
|
+
const cell = faceCellPx(f);
|
|
3522
|
+
const fade = FOREHEAD_FADE * f.hh;
|
|
3523
|
+
const sides = [-1, 1];
|
|
3524
|
+
const hold = sides.map(() => new Float64Array(count));
|
|
3525
|
+
const whole = sides.map(() => new Float64Array(count));
|
|
3526
|
+
const span3 = sides.map(() => new Float64Array(count));
|
|
3527
|
+
let any = false;
|
|
3528
|
+
for (let i = 0; i < count; i++) {
|
|
3529
|
+
const y = top - i - 0.5;
|
|
3530
|
+
const faceRuns = f.faceRuns(y);
|
|
3531
|
+
const hairRuns = f.hairRuns("hair_front", y);
|
|
3532
|
+
sides.forEach((side, k) => {
|
|
3533
|
+
const edge = outerEdge(faceRuns, f.axisX, side);
|
|
3534
|
+
const hair = outerEdge(hairRuns, f.axisX, side);
|
|
3535
|
+
if (edge === void 0 || hair === void 0) return;
|
|
3536
|
+
const end = side * (edge - f.axisX) - cell;
|
|
3537
|
+
if (end <= 0) return;
|
|
3538
|
+
const cover = side * (hair - edge);
|
|
3539
|
+
const room = Math.max(0, cover);
|
|
3540
|
+
const goes = side * plate(edge - side * 0.5, y, 30 * side, 0)[0];
|
|
3541
|
+
const held = side * front(hair - side * 0.5, y, 30 * side, 0)[0];
|
|
3542
|
+
const need = clamp(1 + cover / fade, 0, 1) * Math.max(0, goes - held - room + Math.min(1, room));
|
|
3543
|
+
if (need <= 0) return;
|
|
3544
|
+
const over = Math.min(end, Math.max(2 * cell, end - inner, 2 * need));
|
|
3545
|
+
hold[k][i] = Math.min(need, over / 2);
|
|
3546
|
+
whole[k][i] = end;
|
|
3547
|
+
span3[k][i] = over;
|
|
3548
|
+
any = true;
|
|
3549
|
+
});
|
|
3550
|
+
}
|
|
3551
|
+
if (!any) return void 0;
|
|
3552
|
+
const reach = Math.ceil(cell);
|
|
3553
|
+
return (x, y, s) => {
|
|
3554
|
+
const u = s * (x - f.axisX);
|
|
3555
|
+
if (s === 0 || u <= 0 || y <= floor) return 0;
|
|
3556
|
+
const k = s < 0 ? 0 : 1;
|
|
3557
|
+
const i = clamp(Math.floor(top - y), 0, count - 1);
|
|
3558
|
+
let h = 0;
|
|
3559
|
+
const j1 = Math.min(count - 1, i + 3 * reach);
|
|
3560
|
+
for (let j = Math.max(0, i - 3 * reach); j <= j1; j++) {
|
|
3561
|
+
if (hold[k][j] === 0) continue;
|
|
3562
|
+
const near = clamp((3 * reach - Math.abs(i - j)) / (2 * reach), 0, 1);
|
|
3563
|
+
const across = clamp((u - (whole[k][j] - span3[k][j])) / span3[k][j], 0, 1);
|
|
3564
|
+
h = Math.max(h, hold[k][j] * near * across);
|
|
3565
|
+
}
|
|
3566
|
+
return h * clamp((y - floor) / (2 * cell), 0, 1);
|
|
3567
|
+
};
|
|
3568
|
+
}
|
|
3473
3569
|
function earField(m) {
|
|
3474
3570
|
const f = m.frame;
|
|
3475
3571
|
const ears = f.ears;
|
|
@@ -3483,7 +3579,7 @@ function earField(m) {
|
|
|
3483
3579
|
const attach = ears.attachAt(y);
|
|
3484
3580
|
const span3 = Math.max(1, ears.outer - attach);
|
|
3485
3581
|
if (sideOf(m, x, s) === "far") {
|
|
3486
|
-
const rim2 =
|
|
3582
|
+
const rim2 = m.earFar * m.face;
|
|
3487
3583
|
const root2 = earRoot(m, face, y, s);
|
|
3488
3584
|
const lead = Math.max(
|
|
3489
3585
|
0,
|
|
@@ -3508,7 +3604,7 @@ function farEarCovered(m) {
|
|
|
3508
3604
|
const ears = m.frame.ears;
|
|
3509
3605
|
if (ears === void 0) return true;
|
|
3510
3606
|
const face = faceField(m);
|
|
3511
|
-
const rim =
|
|
3607
|
+
const rim = m.earFar * m.face;
|
|
3512
3608
|
const y0 = ears.bottom - 2;
|
|
3513
3609
|
const y1 = ears.top + 2;
|
|
3514
3610
|
const n = Math.max(1, Math.ceil(y1 - y0));
|
|
@@ -4349,6 +4445,7 @@ function fitTurn(ctx, targets, plan, build) {
|
|
|
4349
4445
|
lead: plan.turn.featureLead,
|
|
4350
4446
|
follow: plan.hair.follow,
|
|
4351
4447
|
outline: plan.hair.outlineFollow,
|
|
4448
|
+
earFar: plan.turn.farEar,
|
|
4352
4449
|
fore: targets.farEyeRatio === void 0 ? plan.turn.eyeForeshorten : 1,
|
|
4353
4450
|
shell: 1,
|
|
4354
4451
|
noseLead: targets.noseShift !== void 0 || plan.turn.noseLead === 1 ? void 0 : plan.turn.noseLead
|
|
@@ -4585,6 +4682,7 @@ function buildTurn(frame, landmarks, q, turn) {
|
|
|
4585
4682
|
hairOuter: q.outline * face,
|
|
4586
4683
|
hairBack: A * hh * turn.hairBack,
|
|
4587
4684
|
shellScale: q.shell,
|
|
4685
|
+
earFar: q.earFar,
|
|
4588
4686
|
earFarScale: 1 + A * (turn.earFarScale - 1),
|
|
4589
4687
|
...landmarks
|
|
4590
4688
|
};
|
|
@@ -4672,6 +4770,23 @@ var RIG_PLAN_FIELDS = [
|
|
|
4672
4770
|
migratedFrom: null,
|
|
4673
4771
|
what: "how far the nose leads the face plate on the turn \u2014 turn only, the nod keeps the profile's nose depth"
|
|
4674
4772
|
},
|
|
4773
|
+
{
|
|
4774
|
+
path: "turn.farEar",
|
|
4775
|
+
group: "turn",
|
|
4776
|
+
key: "farEar",
|
|
4777
|
+
kind: "absolute",
|
|
4778
|
+
unit: "share of the face plate's slide",
|
|
4779
|
+
default: PROFILE.TURN.earFar,
|
|
4780
|
+
range: [0, 1],
|
|
4781
|
+
recommended: [0, 0.5],
|
|
4782
|
+
step: 0.1,
|
|
4783
|
+
poses: TURN_POSES,
|
|
4784
|
+
cue: null,
|
|
4785
|
+
fitBy: null,
|
|
4786
|
+
provisional: false,
|
|
4787
|
+
migratedFrom: null,
|
|
4788
|
+
what: "how far the far ear's outer edge follows the face on the turn (0: it holds and the head slides over it, about half of a default-sized ear going under the face; 1: it rides with the face and stays whole)"
|
|
4789
|
+
},
|
|
4675
4790
|
{
|
|
4676
4791
|
path: "hair.follow",
|
|
4677
4792
|
group: "hair",
|
|
@@ -6758,6 +6873,11 @@ function buildFaceMesh(frame, b, head, neckCells) {
|
|
|
6758
6873
|
frame.axisX - frame.headMax + 0.5,
|
|
6759
6874
|
frame.axisX + frame.headMax - 0.5
|
|
6760
6875
|
];
|
|
6876
|
+
const ears2 = frame.ears;
|
|
6877
|
+
if (ears2 !== void 0 && neck2 === void 0) {
|
|
6878
|
+
const d = ears2.edgeAt(ears2.bottom);
|
|
6879
|
+
at.push(frame.axisX - d, frame.axisX + d);
|
|
6880
|
+
}
|
|
6761
6881
|
if (neck2 !== void 0) {
|
|
6762
6882
|
at.push(
|
|
6763
6883
|
frame.axisX,
|
|
@@ -7376,7 +7496,7 @@ function writeOcclusion(customParts, built, parts) {
|
|
|
7376
7496
|
|
|
7377
7497
|
// src/auto-rig/generate.ts
|
|
7378
7498
|
var MESH_CELLS = {
|
|
7379
|
-
face: { px:
|
|
7499
|
+
face: { px: FACE_CELL_PX, min: 4, max: 24 },
|
|
7380
7500
|
// The front hair eases from riding the face to holding the outline.
|
|
7381
7501
|
hair_front: { px: 32, min: 3, max: 24 },
|
|
7382
7502
|
// A side lock's rows come from `sideLockGrid`: even ones, plus the root
|
|
@@ -7684,7 +7804,7 @@ function buildPart(spec, b, order, frame, byRole, plan, frontGrid, lips) {
|
|
|
7684
7804
|
let region;
|
|
7685
7805
|
let headStart = 0;
|
|
7686
7806
|
if (spec.role === "face") {
|
|
7687
|
-
const px =
|
|
7807
|
+
const px = faceCellPx(frame);
|
|
7688
7808
|
const fm = buildFaceMesh(frame, b, { ...MESH_CELLS.face, px }, NECK_CELLS);
|
|
7689
7809
|
mesh = fm.mesh;
|
|
7690
7810
|
region = fm.region;
|