@ikijs/editor 0.7.0 → 0.8.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 +6 -1
- package/dist/index.d.mts +30 -6
- package/dist/index.d.ts +30 -6
- package/dist/index.js +144 -45
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +144 -45
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1776,6 +1776,45 @@ function validateLayerInputs(layers, canvas) {
|
|
|
1776
1776
|
}
|
|
1777
1777
|
}
|
|
1778
1778
|
}
|
|
1779
|
+
if (layer.denseCore !== void 0) {
|
|
1780
|
+
const where = `auto-rig: validateLayerInputs: role "${role}" denseCore`;
|
|
1781
|
+
const core = layer.denseCore;
|
|
1782
|
+
if (typeof core !== "object" || core === null || Array.isArray(core)) {
|
|
1783
|
+
throw new Error(`${where} must be a plain object { x, y, w, h }`);
|
|
1784
|
+
}
|
|
1785
|
+
for (const field of ["x", "y", "w", "h"]) {
|
|
1786
|
+
const v = core[field];
|
|
1787
|
+
const size = field === "w" || field === "h";
|
|
1788
|
+
if (typeof v !== "number" || !Number.isFinite(v) || size && v <= 0) {
|
|
1789
|
+
throw new Error(
|
|
1790
|
+
`${where}.${field} is ${String(v)}, not a finite number${size ? " > 0" : ""}`
|
|
1791
|
+
);
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
const { x, y, w, h } = layer.denseCore;
|
|
1795
|
+
const right = bbox.x + bbox.w;
|
|
1796
|
+
const bottom = bbox.y + bbox.h;
|
|
1797
|
+
if (x < bbox.x) {
|
|
1798
|
+
throw new Error(
|
|
1799
|
+
`${where}.x (${x}) lies left of the bbox's left edge (${bbox.x})`
|
|
1800
|
+
);
|
|
1801
|
+
}
|
|
1802
|
+
if (y < bbox.y) {
|
|
1803
|
+
throw new Error(
|
|
1804
|
+
`${where}.y (${y}) lies above the bbox's top edge (${bbox.y})`
|
|
1805
|
+
);
|
|
1806
|
+
}
|
|
1807
|
+
if (x + w > right) {
|
|
1808
|
+
throw new Error(
|
|
1809
|
+
`${where}.w (${w}) puts its right edge at ${x + w}, past the bbox's right edge (${right})`
|
|
1810
|
+
);
|
|
1811
|
+
}
|
|
1812
|
+
if (y + h > bottom) {
|
|
1813
|
+
throw new Error(
|
|
1814
|
+
`${where}.h (${h}) puts its bottom edge at ${y + h}, past the bbox's bottom edge (${bottom})`
|
|
1815
|
+
);
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1779
1818
|
}
|
|
1780
1819
|
}
|
|
1781
1820
|
function validateStrandEdges(layers, strandEdges) {
|
|
@@ -1952,6 +1991,7 @@ function meshCellsFor(w, h) {
|
|
|
1952
1991
|
}
|
|
1953
1992
|
var FACE_PLATE_CELLS = 10;
|
|
1954
1993
|
var FEATURE_GRID_CELLS = 4;
|
|
1994
|
+
var MOUTH_GRID_CELL_PX = 32;
|
|
1955
1995
|
var TURN_LATTICE_CELL_PX = 16;
|
|
1956
1996
|
var GRID_MARGIN = 0.12;
|
|
1957
1997
|
var HEAD_CYLINDER_RADIUS_FACTOR = 0.6 / 0.5;
|
|
@@ -2102,31 +2142,33 @@ function turnColumnMap(grid, faceCenterX, radiusX, angleX, travel) {
|
|
|
2102
2142
|
x + boundedCylinderBend(x - faceCenterX, radiusX, theta) + slide
|
|
2103
2143
|
);
|
|
2104
2144
|
}
|
|
2105
|
-
const cellFor = (
|
|
2145
|
+
const cellFor = (x) => {
|
|
2106
2146
|
let lo = 0;
|
|
2107
2147
|
let hi = grid.cols - 1;
|
|
2108
2148
|
while (lo < hi) {
|
|
2109
2149
|
const mid = lo + hi >> 1;
|
|
2110
|
-
if (
|
|
2150
|
+
if (x < restX[mid + 1]) hi = mid;
|
|
2111
2151
|
else lo = mid + 1;
|
|
2112
2152
|
}
|
|
2113
2153
|
return lo;
|
|
2114
2154
|
};
|
|
2115
|
-
const sample = (edges, values, v) => {
|
|
2116
|
-
const c = cellFor(v, edges);
|
|
2117
|
-
const s = Math.max(
|
|
2118
|
-
0,
|
|
2119
|
-
Math.min(1, (v - edges[c]) / (edges[c + 1] - edges[c]))
|
|
2120
|
-
);
|
|
2121
|
-
if (s === 0) return values[c];
|
|
2122
|
-
if (s === 1) return values[c + 1];
|
|
2123
|
-
return values[c] + (values[c + 1] - values[c]) * s;
|
|
2124
|
-
};
|
|
2125
2155
|
return {
|
|
2126
2156
|
restX,
|
|
2127
2157
|
warpedX,
|
|
2128
|
-
|
|
2129
|
-
|
|
2158
|
+
// Piecewise-linear read of `warpedX` over `restX`, the within-cell
|
|
2159
|
+
// fraction clamped to [0,1] as sampleWarpGrid clamps (s, t). A node reads
|
|
2160
|
+
// its own warped x: `warpedX[c] + Δ·1` is `warpedX[c + 1]` only to an
|
|
2161
|
+
// ulp, and the columns a bake reads are nodes.
|
|
2162
|
+
mapX: (x) => {
|
|
2163
|
+
const c = cellFor(x);
|
|
2164
|
+
const s = Math.max(
|
|
2165
|
+
0,
|
|
2166
|
+
Math.min(1, (x - restX[c]) / (restX[c + 1] - restX[c]))
|
|
2167
|
+
);
|
|
2168
|
+
if (s === 0) return warpedX[c];
|
|
2169
|
+
if (s === 1) return warpedX[c + 1];
|
|
2170
|
+
return warpedX[c] + (warpedX[c + 1] - warpedX[c]) * s;
|
|
2171
|
+
}
|
|
2130
2172
|
};
|
|
2131
2173
|
}
|
|
2132
2174
|
function renderedLandingX(part, vertexLanding, px, py) {
|
|
@@ -2232,7 +2274,7 @@ function plateLandingOn(plate, mapAt) {
|
|
|
2232
2274
|
y
|
|
2233
2275
|
);
|
|
2234
2276
|
}
|
|
2235
|
-
function plateGuardRowsFor(face, hairFront) {
|
|
2277
|
+
function plateGuardRowsFor(face, hairFront, widestY = -Infinity) {
|
|
2236
2278
|
const rowsOf = (part, y) => {
|
|
2237
2279
|
const { rows } = meshCellsFor(part.cropW, part.cropH);
|
|
2238
2280
|
return Array.from(
|
|
@@ -2247,7 +2289,7 @@ function plateGuardRowsFor(face, hairFront) {
|
|
|
2247
2289
|
return [
|
|
2248
2290
|
...faceRows,
|
|
2249
2291
|
...rowsOf(hairFront, hairFront.centerY).map(
|
|
2250
|
-
(y) => Math.max(bottom, Math.min(top, y))
|
|
2292
|
+
(y) => Math.max(bottom, Math.min(top, Math.max(y, widestY)))
|
|
2251
2293
|
)
|
|
2252
2294
|
];
|
|
2253
2295
|
}
|
|
@@ -2259,7 +2301,7 @@ function plateGuardsOf(carriers, faceHalfWidth, profile, hairFront) {
|
|
|
2259
2301
|
return {
|
|
2260
2302
|
plate,
|
|
2261
2303
|
edgeAt: profile === void 0 ? () => faceHalfWidth : profile.at,
|
|
2262
|
-
plateGuardRows: plateGuardRowsFor(plate.part, hairFront)
|
|
2304
|
+
plateGuardRows: plateGuardRowsFor(plate.part, hairFront, profile?.widestY)
|
|
2263
2305
|
};
|
|
2264
2306
|
}
|
|
2265
2307
|
var TurnTargetError = class extends Error {
|
|
@@ -2330,12 +2372,17 @@ function resolveTurnTargets(targets = {}) {
|
|
|
2330
2372
|
defaulted
|
|
2331
2373
|
};
|
|
2332
2374
|
}
|
|
2375
|
+
function noseDrawnBox(layer) {
|
|
2376
|
+
return layer.denseCore ?? layer.bbox;
|
|
2377
|
+
}
|
|
2333
2378
|
function turnLandmarks(layers, carriers) {
|
|
2334
2379
|
const markOf = (role) => {
|
|
2335
2380
|
const layer = layers.find((l) => l.role === role);
|
|
2336
2381
|
if (!layer) return void 0;
|
|
2337
|
-
const
|
|
2338
|
-
const
|
|
2382
|
+
const drawn = role === "nose" ? noseDrawnBox(layer) : layer.bbox;
|
|
2383
|
+
const t = bboxToTransform(drawn, layer.canvasW, layer.canvasH, role);
|
|
2384
|
+
const w = drawn === layer.bbox ? layer.cropW : drawn.w;
|
|
2385
|
+
const mark = { x: t.x, y: t.y, w };
|
|
2339
2386
|
const carrier = carriers?.get(role);
|
|
2340
2387
|
if (carrier !== void 0) mark.carrier = carrier;
|
|
2341
2388
|
return mark;
|
|
@@ -2432,14 +2479,15 @@ var TURN_SWEEP_SAMPLES = 32;
|
|
|
2432
2479
|
var HOLD_CLEARANCE = 1;
|
|
2433
2480
|
var TURN_HOLD_SAMPLES = 16;
|
|
2434
2481
|
var TURN_SETTLE_PASSES = 24;
|
|
2435
|
-
function hairFrontHoldTarget(x, y, deg, faceCenterX, plateLandingAt, edgeAt, holdBase, holdEdge) {
|
|
2482
|
+
function hairFrontHoldTarget(x, y, deg, faceCenterX, plateLandingAt, edgeAt, holdBase, holdEdge, widestY = -Infinity) {
|
|
2436
2483
|
const local = x - faceCenterX;
|
|
2437
2484
|
const dist = Math.abs(local);
|
|
2438
2485
|
const side = Math.sign(local);
|
|
2439
|
-
const
|
|
2440
|
-
|
|
2486
|
+
const row = Math.max(y, widestY);
|
|
2487
|
+
const painted = edgeAt(row);
|
|
2488
|
+
if (dist <= painted) return plateLandingAt(deg, x, row);
|
|
2441
2489
|
if (dist <= holdBase) {
|
|
2442
|
-
const inner = plateLandingAt(deg, faceCenterX + side * painted,
|
|
2490
|
+
const inner = plateLandingAt(deg, faceCenterX + side * painted, row);
|
|
2443
2491
|
const u = (dist - painted) / (holdBase - painted);
|
|
2444
2492
|
return inner + (faceCenterX + side * holdEdge - inner) * u;
|
|
2445
2493
|
}
|
|
@@ -2544,7 +2592,8 @@ function evaluateTurnCandidate(ctx, radius) {
|
|
|
2544
2592
|
plateLandingAt,
|
|
2545
2593
|
ctx.edgeAt,
|
|
2546
2594
|
hold.holdBase,
|
|
2547
|
-
hold.holdEdgeAt(deg)
|
|
2595
|
+
hold.holdEdgeAt(deg),
|
|
2596
|
+
ctx.profile?.widestY
|
|
2548
2597
|
),
|
|
2549
2598
|
leadShift
|
|
2550
2599
|
);
|
|
@@ -3286,6 +3335,7 @@ function solveTurnModel(targets, landmarks, lattice, faceCenterX, faceHalfWidth,
|
|
|
3286
3335
|
var EYE_STACK_PREFIXES = ["eye_", "iris_", "pupil_", "highlight_"];
|
|
3287
3336
|
var HAIR_FRONT_DEPTH = 0.1;
|
|
3288
3337
|
var MOUTH_TURN_TILT_DEG = 5;
|
|
3338
|
+
var NOSE_TURN_TILT_DEG = 6;
|
|
3289
3339
|
var NOD_TRAVEL = 30;
|
|
3290
3340
|
var NOD_BEND = 0.5;
|
|
3291
3341
|
var HAIR_BACK_NOD_DEPTH = -0.135;
|
|
@@ -3518,7 +3568,7 @@ function plateReachAt(plateLandingAt, faceCenterX, edgeAt, plateGuardRows, deg)
|
|
|
3518
3568
|
)
|
|
3519
3569
|
);
|
|
3520
3570
|
}
|
|
3521
|
-
function bakeHairFrontSilhouetteWarp(mesh, partX, partY, faceCenterX, plateLandingAt, edgeAt, holdBase, holdEdgeAt, plateGuardRows) {
|
|
3571
|
+
function bakeHairFrontSilhouetteWarp(mesh, partX, partY, faceCenterX, plateLandingAt, edgeAt, holdBase, holdEdgeAt, plateGuardRows, widestY = -Infinity) {
|
|
3522
3572
|
if (holdEdgeAt(0) !== holdBase) {
|
|
3523
3573
|
throw new Error(
|
|
3524
3574
|
`auto-rig: bakeHairFrontSilhouetteWarp: holdEdgeAt(0) is ${holdEdgeAt(0)}, not the hold edge's own rest distance ${holdBase}, so the bangs would move in the rest pose`
|
|
@@ -3556,7 +3606,8 @@ function bakeHairFrontSilhouetteWarp(mesh, partX, partY, faceCenterX, plateLandi
|
|
|
3556
3606
|
plateLandingAt,
|
|
3557
3607
|
edgeAt,
|
|
3558
3608
|
holdBase,
|
|
3559
|
-
holdEdge
|
|
3609
|
+
holdEdge,
|
|
3610
|
+
widestY
|
|
3560
3611
|
) - x,
|
|
3561
3612
|
0
|
|
3562
3613
|
);
|
|
@@ -3735,7 +3786,7 @@ function defaultPoseOf(bindings) {
|
|
|
3735
3786
|
return binding.from + (binding.to - binding.from) * t;
|
|
3736
3787
|
});
|
|
3737
3788
|
}
|
|
3738
|
-
function groupGridFor(members, cells, turnShiftPx, nodShiftPx) {
|
|
3789
|
+
function groupGridFor(members, cells, turnShiftPx, nodShiftPx, cellPx) {
|
|
3739
3790
|
let minX = Infinity;
|
|
3740
3791
|
let maxX = -Infinity;
|
|
3741
3792
|
let minY = Infinity;
|
|
@@ -3770,24 +3821,50 @@ function groupGridFor(members, cells, turnShiftPx, nodShiftPx) {
|
|
|
3770
3821
|
}
|
|
3771
3822
|
}
|
|
3772
3823
|
}
|
|
3824
|
+
const extentMinX = minX;
|
|
3825
|
+
const extentMaxX = maxX;
|
|
3773
3826
|
minX -= turnShiftPx;
|
|
3774
3827
|
maxX += turnShiftPx;
|
|
3775
3828
|
minY -= nodShiftPx;
|
|
3776
3829
|
maxY += nodShiftPx;
|
|
3777
3830
|
const marginX = (maxX - minX) * GRID_MARGIN + 1;
|
|
3778
3831
|
const marginY = (maxY - minY) * GRID_MARGIN + 1;
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3832
|
+
const points = generateGridPoints(
|
|
3833
|
+
cells,
|
|
3834
|
+
cells,
|
|
3835
|
+
minX - marginX,
|
|
3836
|
+
maxX + marginX,
|
|
3837
|
+
minY - marginY,
|
|
3838
|
+
maxY + marginY
|
|
3839
|
+
);
|
|
3840
|
+
if (cellPx === void 0) return { cols: cells, rows: cells, points };
|
|
3841
|
+
const stride = cells + 1;
|
|
3842
|
+
const uniformXs = Array.from({ length: stride }, (_, c) => points[c * 2]);
|
|
3843
|
+
const ys = Array.from(
|
|
3844
|
+
{ length: stride },
|
|
3845
|
+
(_, r) => points[r * stride * 2 + 1]
|
|
3846
|
+
);
|
|
3847
|
+
const xs = [uniformXs[0]];
|
|
3848
|
+
for (let c = 0; c < cells; c++) {
|
|
3849
|
+
const left = uniformXs[c];
|
|
3850
|
+
const right = uniformXs[c + 1];
|
|
3851
|
+
const from = Math.max(left, extentMinX);
|
|
3852
|
+
const to = Math.min(right, extentMaxX);
|
|
3853
|
+
if (right - left > cellPx && from < to) {
|
|
3854
|
+
const steps = Math.ceil((to - from) / cellPx);
|
|
3855
|
+
if (from > left) xs.push(from);
|
|
3856
|
+
for (let k = 1; k < steps; k++) {
|
|
3857
|
+
xs.push(from + k * (to - from) / steps);
|
|
3858
|
+
}
|
|
3859
|
+
if (to < right) xs.push(to);
|
|
3860
|
+
}
|
|
3861
|
+
xs.push(right);
|
|
3862
|
+
}
|
|
3863
|
+
const merged = [];
|
|
3864
|
+
for (const y of ys) {
|
|
3865
|
+
for (const x of xs) merged.push(x, y);
|
|
3866
|
+
}
|
|
3867
|
+
return { cols: xs.length - 1, rows: cells, points: merged };
|
|
3791
3868
|
}
|
|
3792
3869
|
function eyelidFoldFor(layers, layer, mesh, t) {
|
|
3793
3870
|
const side = ROLE_TABLE[layer.role].eyeSide;
|
|
@@ -3888,7 +3965,8 @@ function turnSetup(layers) {
|
|
|
3888
3965
|
own,
|
|
3889
3966
|
isPlate ? FACE_PLATE_CELLS : FEATURE_GRID_CELLS,
|
|
3890
3967
|
isPlate ? 0 : turnShift[turnFamily(role)],
|
|
3891
|
-
nodShiftPx
|
|
3968
|
+
nodShiftPx,
|
|
3969
|
+
group === "mouthWarp" ? MOUTH_GRID_CELL_PX : void 0
|
|
3892
3970
|
)
|
|
3893
3971
|
);
|
|
3894
3972
|
}
|
|
@@ -3898,11 +3976,31 @@ function turnSetup(layers) {
|
|
|
3898
3976
|
y: mouthAt.y,
|
|
3899
3977
|
tiltDeg: MOUTH_TURN_TILT_DEG
|
|
3900
3978
|
};
|
|
3979
|
+
const noseLayer = layers.find((l) => l.role === "nose");
|
|
3980
|
+
let noseAnchor;
|
|
3981
|
+
if (noseLayer) {
|
|
3982
|
+
const drawn = noseDrawnBox(noseLayer);
|
|
3983
|
+
const at = bboxToTransform(
|
|
3984
|
+
drawn,
|
|
3985
|
+
noseLayer.canvasW,
|
|
3986
|
+
noseLayer.canvasH,
|
|
3987
|
+
"nose"
|
|
3988
|
+
);
|
|
3989
|
+
noseAnchor = {
|
|
3990
|
+
x: at.x,
|
|
3991
|
+
y: at.y + drawn.h / 2,
|
|
3992
|
+
tiltDeg: NOSE_TURN_TILT_DEG
|
|
3993
|
+
};
|
|
3994
|
+
}
|
|
3901
3995
|
const carriers = new Map(
|
|
3902
|
-
members.map((m) =>
|
|
3903
|
-
m.
|
|
3904
|
-
m.group === "mouthWarp" ?
|
|
3905
|
-
|
|
3996
|
+
members.map((m) => {
|
|
3997
|
+
const grid = groupGrids.get(m.group);
|
|
3998
|
+
const anchor = m.group === "mouthWarp" ? mouthAnchor : m.group === "noseWarp" ? noseAnchor : void 0;
|
|
3999
|
+
return [
|
|
4000
|
+
m.role,
|
|
4001
|
+
anchor === void 0 ? { grid, part: m.part } : { grid, part: m.part, anchor }
|
|
4002
|
+
];
|
|
4003
|
+
})
|
|
3906
4004
|
);
|
|
3907
4005
|
const maxShift = Math.max(turnShift.eye, turnShift.nose, turnShift.mouth);
|
|
3908
4006
|
let reach = 0;
|
|
@@ -4208,7 +4306,8 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
4208
4306
|
edgeAt,
|
|
4209
4307
|
holdBase,
|
|
4210
4308
|
turn?.holdEdgeAt ?? (() => holdBase),
|
|
4211
|
-
plateGuardRows
|
|
4309
|
+
plateGuardRows,
|
|
4310
|
+
profile?.widestY
|
|
4212
4311
|
)
|
|
4213
4312
|
);
|
|
4214
4313
|
ownWarps.push(
|