@ikijs/editor 0.12.1 → 0.13.0
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 +14 -11
- package/dist/index.d.mts +20 -21
- package/dist/index.d.ts +20 -21
- package/dist/index.js +355 -131
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +355 -131
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1890,14 +1890,155 @@ function createDefaultWarpDeformer(model) {
|
|
|
1890
1890
|
};
|
|
1891
1891
|
}
|
|
1892
1892
|
|
|
1893
|
+
// src/auto-rig/profile.ts
|
|
1894
|
+
var PROFILE_VALUES = {
|
|
1895
|
+
slide: 0.12,
|
|
1896
|
+
lead: 0.1,
|
|
1897
|
+
noseDepth: 2,
|
|
1898
|
+
hairFollow: 1,
|
|
1899
|
+
fore: 0.2,
|
|
1900
|
+
backShare: 0.1,
|
|
1901
|
+
earFar: 0.5,
|
|
1902
|
+
nodDown: 0.18,
|
|
1903
|
+
nodUp: 0.1,
|
|
1904
|
+
nodLead: 0.06,
|
|
1905
|
+
chinShare: 0.75,
|
|
1906
|
+
rollDeg: 14,
|
|
1907
|
+
gaze: 0.2,
|
|
1908
|
+
blink: 0.6,
|
|
1909
|
+
mouthOpen: 0.8,
|
|
1910
|
+
mouthWiden: 1.2,
|
|
1911
|
+
brow: 0.15,
|
|
1912
|
+
breath: 0.03,
|
|
1913
|
+
sway: 0.08
|
|
1914
|
+
};
|
|
1915
|
+
function deriveProfile(v) {
|
|
1916
|
+
const farLead = v.lead * (1 - v.fore / 2);
|
|
1917
|
+
const nearLead = v.lead * (1 + v.fore / 2);
|
|
1918
|
+
const turn = {
|
|
1919
|
+
/** The face plate's translation. */
|
|
1920
|
+
face: v.slide,
|
|
1921
|
+
/** The chin tip's lead over the plate. */
|
|
1922
|
+
chinLead: v.lead,
|
|
1923
|
+
/** The plate's width at full turn: eye and cheek rows, then the jaw. The
|
|
1924
|
+
* plate translates; it does not narrow. */
|
|
1925
|
+
widthUpper: 1,
|
|
1926
|
+
widthJaw: 1,
|
|
1927
|
+
eyeFar: v.slide + farLead,
|
|
1928
|
+
eyeNear: v.slide + nearLead,
|
|
1929
|
+
/** Each eye's width at full turn, about its own centre. */
|
|
1930
|
+
eyeFarScale: 1 - v.fore,
|
|
1931
|
+
eyeNearScale: 1 + v.fore / 2,
|
|
1932
|
+
browFar: v.slide + farLead,
|
|
1933
|
+
browNear: v.slide + nearLead,
|
|
1934
|
+
nose: v.slide + v.noseDepth * v.lead,
|
|
1935
|
+
mouth: v.slide + v.lead,
|
|
1936
|
+
mouthWidth: 1,
|
|
1937
|
+
/** The mouth's far corner rises (its near end drops) by this much: it
|
|
1938
|
+
* stays level. */
|
|
1939
|
+
mouthTiltDeg: 0,
|
|
1940
|
+
/** The front hair (bangs and side locks) rides the face at this share of
|
|
1941
|
+
* its translation. */
|
|
1942
|
+
hairFollow: v.hairFollow,
|
|
1943
|
+
/** The head's outline at the eye row holds: where the front hair draws
|
|
1944
|
+
* it, its outer edge follows the face this much on a row where no back
|
|
1945
|
+
* hair paints behind that edge. Where back hair does, the edge rides
|
|
1946
|
+
* further, as far as that back hair stays behind it through the turn and
|
|
1947
|
+
* the nod, up to the face's follow there (`hairFollow`; over the far
|
|
1948
|
+
* eye's rows, as far as that eye's outer corner goes). On the cap (the
|
|
1949
|
+
* rows above the eyes) it follows less where the turn would otherwise
|
|
1950
|
+
* carry it further outside its back hair than the nod alone does. */
|
|
1951
|
+
outlineFollow: 0,
|
|
1952
|
+
/** The back hair's slight counter-motion. */
|
|
1953
|
+
hairBack: -v.backShare * v.slide,
|
|
1954
|
+
/** The ears lag the plate: the far one's outer edge moves this share of
|
|
1955
|
+
* its slide; the near one rides the head. */
|
|
1956
|
+
earFar: v.earFar,
|
|
1957
|
+
earNear: 1,
|
|
1958
|
+
/** The far ear's width at full turn, about its outer edge, where the head
|
|
1959
|
+
* leaves it room; less where it does not. */
|
|
1960
|
+
earFarScale: 1 - v.fore
|
|
1961
|
+
};
|
|
1962
|
+
const mid = v.nodDown * (1 + v.chinShare) / 2;
|
|
1963
|
+
const feature = (depth) => ({
|
|
1964
|
+
up: -(v.nodUp + depth * v.nodLead),
|
|
1965
|
+
down: mid + depth * v.nodLead
|
|
1966
|
+
});
|
|
1967
|
+
const nod = {
|
|
1968
|
+
faceTop: { up: -v.nodUp, down: v.nodDown },
|
|
1969
|
+
chin: { up: -v.nodUp, down: v.chinShare * v.nodDown },
|
|
1970
|
+
eye: feature(1),
|
|
1971
|
+
/** Eye height at the extreme, about the eye's centre: it holds. */
|
|
1972
|
+
eyeHeight: { up: 1, down: 1 },
|
|
1973
|
+
brow: feature(1),
|
|
1974
|
+
nose: feature(v.noseDepth),
|
|
1975
|
+
mouth: feature(1),
|
|
1976
|
+
hairFront: { up: -v.hairFollow * v.nodUp, down: v.hairFollow * v.nodDown },
|
|
1977
|
+
/** The front hair's cap top: half the plate's rise looking up; looking
|
|
1978
|
+
* down the plate's top, which the rig slides only as far as the back
|
|
1979
|
+
* hair covers the crown behind it. */
|
|
1980
|
+
hairFrontTop: { up: -v.nodUp / 2, down: v.nodDown },
|
|
1981
|
+
hairBack: { up: -v.backShare * v.nodUp, down: v.backShare * v.nodDown }
|
|
1982
|
+
};
|
|
1983
|
+
const amplitude = {
|
|
1984
|
+
/** Iris travel, in iris widths. */
|
|
1985
|
+
gazeX: v.gaze,
|
|
1986
|
+
gazeY: v.gaze / 2,
|
|
1987
|
+
/** How far the upper lid comes down, over the eye's height. */
|
|
1988
|
+
blink: v.blink,
|
|
1989
|
+
/** A single mouth drawing opens to this height over its rest width. */
|
|
1990
|
+
mouthOpenHeight: v.mouthOpen,
|
|
1991
|
+
/** The mouth widens this much open. */
|
|
1992
|
+
mouthOpenWidth: v.mouthWiden,
|
|
1993
|
+
/** Brow raise / lower, hh. */
|
|
1994
|
+
brow: v.brow,
|
|
1995
|
+
/** Breath lifts the shoulders and the head, hh. */
|
|
1996
|
+
breathBody: v.breath,
|
|
1997
|
+
breathHead: v.breath * 2 / 3,
|
|
1998
|
+
/** Hair tip travel at full sway (±20), hh. */
|
|
1999
|
+
sway: v.sway,
|
|
2000
|
+
/** The neck under the face reaches this far above the jaw, hh — what the
|
|
2001
|
+
* chin uncovers as it slides off it: half again the chin's full-turn
|
|
2002
|
+
* slide, to the next 0.05. */
|
|
2003
|
+
hiddenNeck: Math.ceil(30 * (v.slide + v.lead) - 1e-9) / 20
|
|
2004
|
+
};
|
|
2005
|
+
return { TURN: turn, NOD: nod, ROLL_DEG: v.rollDeg, AMPLITUDE: amplitude };
|
|
2006
|
+
}
|
|
2007
|
+
var { TURN, NOD, ROLL_DEG, AMPLITUDE } = deriveProfile(PROFILE_VALUES);
|
|
2008
|
+
var STYLE_KNOBS = [
|
|
2009
|
+
"turn",
|
|
2010
|
+
"featureLead",
|
|
2011
|
+
"hairFollow",
|
|
2012
|
+
"outlineFollow",
|
|
2013
|
+
"blink",
|
|
2014
|
+
"sway"
|
|
2015
|
+
];
|
|
2016
|
+
function resolveStyle(style, check) {
|
|
2017
|
+
const s = style ?? {};
|
|
2018
|
+
const get = (key, def, lo, hi) => s[key] === void 0 ? def : check(`style.${key}`, s[key], lo, hi);
|
|
2019
|
+
return {
|
|
2020
|
+
turn: get("turn", 1, 0, 3),
|
|
2021
|
+
featureLead: get("featureLead", 1, 0, 3),
|
|
2022
|
+
hairFollow: get("hairFollow", TURN.hairFollow, 0, 2),
|
|
2023
|
+
outlineFollow: get("outlineFollow", TURN.outlineFollow, 0, 2),
|
|
2024
|
+
blink: get("blink", AMPLITUDE.blink, 0.1, 1),
|
|
2025
|
+
sway: get("sway", 1, 0, 5)
|
|
2026
|
+
};
|
|
2027
|
+
}
|
|
2028
|
+
|
|
1893
2029
|
// src/auto-rig/types.ts
|
|
2030
|
+
var round2 = (v) => Math.round(v * 100) / 100;
|
|
1894
2031
|
var TurnTargetError = class extends Error {
|
|
1895
2032
|
constructor(message) {
|
|
1896
2033
|
super(message);
|
|
1897
2034
|
this.name = "TurnTargetError";
|
|
1898
2035
|
}
|
|
1899
2036
|
};
|
|
1900
|
-
var DEFAULT_TURN_TARGETS = Object.freeze({
|
|
2037
|
+
var DEFAULT_TURN_TARGETS = Object.freeze({
|
|
2038
|
+
eyeShift: round2((TURN.eyeFar + TURN.eyeNear) / 2 - TURN.hairBack),
|
|
2039
|
+
farEyeRatio: round2(TURN.eyeFarScale / TURN.eyeNearScale),
|
|
2040
|
+
silhouetteRatio: 1
|
|
2041
|
+
});
|
|
1901
2042
|
|
|
1902
2043
|
// src/auto-rig/roles.ts
|
|
1903
2044
|
var ROLE_TABLE = [
|
|
@@ -2358,110 +2499,6 @@ function land(l, offsets, x, y) {
|
|
|
2358
2499
|
return [topX + (botX - topX) * t, topY + (botY - topY) * t];
|
|
2359
2500
|
}
|
|
2360
2501
|
|
|
2361
|
-
// src/auto-rig/profile.ts
|
|
2362
|
-
var HH_PER_EYE_TO_CHIN = 1.017;
|
|
2363
|
-
var TURN = {
|
|
2364
|
-
/** The face plate's translation (its cheek edges, 0.118 / 0.126). */
|
|
2365
|
-
face: 0.118,
|
|
2366
|
-
/** The chin tip's lead over the plate (it moves 0.194). */
|
|
2367
|
-
chinLead: 0.076,
|
|
2368
|
-
/** The plate's width at full turn: eye and cheek rows, then the jaw. */
|
|
2369
|
-
widthUpper: 0.985,
|
|
2370
|
-
widthJaw: 0.96,
|
|
2371
|
-
eyeFar: 0.192,
|
|
2372
|
-
eyeNear: 0.23,
|
|
2373
|
-
/** Each eye's width at full turn, about its own centre. */
|
|
2374
|
-
eyeFarScale: 0.85,
|
|
2375
|
-
eyeNearScale: 1.085,
|
|
2376
|
-
browFar: 0.213,
|
|
2377
|
-
browNear: 0.243,
|
|
2378
|
-
nose: 0.301,
|
|
2379
|
-
mouth: 0.213,
|
|
2380
|
-
mouthWidth: 0.975,
|
|
2381
|
-
/** The mouth's far corner rises (its near end drops) by this much. */
|
|
2382
|
-
mouthTiltDeg: 4.4,
|
|
2383
|
-
/** The front hair (bangs and side locks) rides the face at this share of
|
|
2384
|
-
* its translation (bangs 1.18×, side locks at the eye row ≈ 1.0×). */
|
|
2385
|
-
hairFollow: 1.1,
|
|
2386
|
-
/** The head's outline at the eye row holds (−0.014 / −0.004 hh): where the
|
|
2387
|
-
* front hair draws it, its outer edge follows the face this much on a row
|
|
2388
|
-
* where no back hair paints behind that edge. Where back hair does, the
|
|
2389
|
-
* edge rides further, as far as that back hair stays behind it through
|
|
2390
|
-
* the turn and the nod, up to the face's follow there (`hairFollow`;
|
|
2391
|
-
* over the far eye's rows, as far as that eye's outer corner goes). */
|
|
2392
|
-
outlineFollow: 0,
|
|
2393
|
-
/** The back hair's slight counter-motion. */
|
|
2394
|
-
hairBack: -0.027,
|
|
2395
|
-
/** The ears lag the plate: the far one moves this share of its slide, the
|
|
2396
|
-
* near one this at its widest reach, its root riding the head (the
|
|
2397
|
-
* samples' parallax ratios, 0.44 and 0.87). */
|
|
2398
|
-
earFar: 0.44,
|
|
2399
|
-
earNear: 0.87
|
|
2400
|
-
};
|
|
2401
|
-
var NOD = {
|
|
2402
|
-
faceTop: { up: -0.081, down: 0.178 },
|
|
2403
|
-
chin: { up: -0.0875, down: 0.109 },
|
|
2404
|
-
eye: { up: -0.167, down: 0.202 },
|
|
2405
|
-
/** Eye height at the extreme, about the eye's centre. */
|
|
2406
|
-
eyeHeight: { up: 0.994, down: 0.96 },
|
|
2407
|
-
brow: { up: -0.167, down: 0.223 },
|
|
2408
|
-
nose: { up: -0.207, down: 0.193 },
|
|
2409
|
-
mouth: { up: -0.172, down: 0.177 },
|
|
2410
|
-
hairFront: { up: -0.11, down: 0.189 },
|
|
2411
|
-
/** The front hair's cap top. The samples' cap top moves 1.215× their face's
|
|
2412
|
-
* centroid looking down (Mao 1.236, Haru 1.626, Hiyori 1.194, Natori
|
|
2413
|
-
* 0.730) and 0.415× looking up (0.385, 0.430, 0.400, 0.487); each ratio
|
|
2414
|
-
* times the plate's own nod halfway between its top and the chin (0.1435
|
|
2415
|
-
* down, 0.084 up). Looking down, the rig slides it only as far as the
|
|
2416
|
-
* back hair covers the crown behind it. */
|
|
2417
|
-
hairFrontTop: { up: -0.035, down: 0.174 },
|
|
2418
|
-
hairBack: { up: -0.011, down: 0.025 }
|
|
2419
|
-
};
|
|
2420
|
-
var ROLL_DEG = 10;
|
|
2421
|
-
var AMPLITUDE = {
|
|
2422
|
-
/** Iris travel, in iris widths (X 0.045 hh, Y 0.031 hh on the samples). */
|
|
2423
|
-
gazeX: 0.17,
|
|
2424
|
-
gazeY: 0.11,
|
|
2425
|
-
/** How far the upper lid comes down, over the eye's height (Haru 0.30,
|
|
2426
|
-
* Mao 0.58, Hiyori 0.67). */
|
|
2427
|
-
blink: 0.58,
|
|
2428
|
-
/** A single mouth drawing opens to this height over its rest width. */
|
|
2429
|
-
mouthOpenHeight: 0.77,
|
|
2430
|
-
/** The mouth widens this much open. */
|
|
2431
|
-
mouthOpenWidth: 1.15,
|
|
2432
|
-
/** Brow raise / lower, hh. */
|
|
2433
|
-
brow: 0.089,
|
|
2434
|
-
/** Breath lifts the shoulders and the head, hh. */
|
|
2435
|
-
breathBody: 0.025,
|
|
2436
|
-
breathHead: 0.016,
|
|
2437
|
-
/** Hair tip travel at full sway (±20), hh (the keyform ranges at ±1 have a
|
|
2438
|
-
* median of 0.05–0.06 hh). */
|
|
2439
|
-
sway: 0.06,
|
|
2440
|
-
/** The neck under the face reaches this far above the jaw, hh — what the
|
|
2441
|
-
* chin uncovers as it slides off it. */
|
|
2442
|
-
hiddenNeck: 0.3
|
|
2443
|
-
};
|
|
2444
|
-
var STYLE_KNOBS = [
|
|
2445
|
-
"turn",
|
|
2446
|
-
"featureLead",
|
|
2447
|
-
"hairFollow",
|
|
2448
|
-
"outlineFollow",
|
|
2449
|
-
"blink",
|
|
2450
|
-
"sway"
|
|
2451
|
-
];
|
|
2452
|
-
function resolveStyle(style, check) {
|
|
2453
|
-
const s = style ?? {};
|
|
2454
|
-
const get = (key, def, lo, hi) => s[key] === void 0 ? def : check(`style.${key}`, s[key], lo, hi);
|
|
2455
|
-
return {
|
|
2456
|
-
turn: get("turn", 1, 0, 3),
|
|
2457
|
-
featureLead: get("featureLead", 1, 0, 3),
|
|
2458
|
-
hairFollow: get("hairFollow", TURN.hairFollow, 0, 2),
|
|
2459
|
-
outlineFollow: get("outlineFollow", TURN.outlineFollow, 0, 2),
|
|
2460
|
-
blink: get("blink", AMPLITUDE.blink, 0.1, 1),
|
|
2461
|
-
sway: get("sway", 1, 0, 5)
|
|
2462
|
-
};
|
|
2463
|
-
}
|
|
2464
|
-
|
|
2465
2502
|
// src/auto-rig/head.ts
|
|
2466
2503
|
function outerEdge(runs, axisX, side) {
|
|
2467
2504
|
if (runs.length === 0) return void 0;
|
|
@@ -2674,7 +2711,7 @@ function buildHeadFrame(layers, opts) {
|
|
|
2674
2711
|
return face.y1;
|
|
2675
2712
|
};
|
|
2676
2713
|
const chin = chinY;
|
|
2677
|
-
const shade = JAW_SHADE *
|
|
2714
|
+
const shade = JAW_SHADE * Math.max(1, eyeY - chin);
|
|
2678
2715
|
const reach = opts.chinSlide === void 0 ? nk.waist : clamp(
|
|
2679
2716
|
nk.waist - opts.chinSlide - CUT_MARGIN - (CUT_LOW + 1),
|
|
2680
2717
|
0,
|
|
@@ -2723,7 +2760,7 @@ function buildHeadFrame(layers, opts) {
|
|
|
2723
2760
|
}
|
|
2724
2761
|
}
|
|
2725
2762
|
}
|
|
2726
|
-
const hh =
|
|
2763
|
+
const hh = Math.max(1, eyeY - chinY);
|
|
2727
2764
|
const headHalf = opts.headHalfWidth;
|
|
2728
2765
|
const hairShell = headHalf !== void 0;
|
|
2729
2766
|
let shellLeft = axisX - plateHalf;
|
|
@@ -2817,16 +2854,45 @@ function earField(m) {
|
|
|
2817
2854
|
const s = Math.sign(ax);
|
|
2818
2855
|
const a = Math.abs(ax) / 30;
|
|
2819
2856
|
const dy = face(x, y, 0, ay)[1];
|
|
2820
|
-
if (sideOf(m, x, s) === "far") return [s * a * TURN.earFar * m.face, dy];
|
|
2821
2857
|
const d = Math.abs(x - f.axisX);
|
|
2822
2858
|
const attach = ears.attachAt(y);
|
|
2859
|
+
const span = Math.max(1, ears.outer - attach);
|
|
2860
|
+
if (sideOf(m, x, s) === "far") {
|
|
2861
|
+
const rim2 = TURN.earFar * m.face;
|
|
2862
|
+
const root2 = earRoot(m, face, y, s);
|
|
2863
|
+
const lead = Math.max(
|
|
2864
|
+
0,
|
|
2865
|
+
Math.min((1 - m.earFarScale) * span, root2 - rim2)
|
|
2866
|
+
);
|
|
2867
|
+
let along = rim2 + lead * (ears.outer - d) / span;
|
|
2868
|
+
if (d < attach) along = Math.min(along, s * face(x, y, 30 * s, 0)[0]);
|
|
2869
|
+
return [s * a * along, dy];
|
|
2870
|
+
}
|
|
2823
2871
|
if (d <= attach) return [face(x, y, ax, 0)[0], dy];
|
|
2824
2872
|
const root = face(f.axisX + Math.sign(x - f.axisX) * attach, y, ax, 0)[0];
|
|
2825
2873
|
const rim = s * a * TURN.earNear * m.face;
|
|
2826
|
-
const t = Math.min(1, (d - attach) /
|
|
2874
|
+
const t = Math.min(1, (d - attach) / span);
|
|
2827
2875
|
return [root + (rim - root) * t, dy];
|
|
2828
2876
|
};
|
|
2829
2877
|
}
|
|
2878
|
+
function earRoot(m, face, y, s) {
|
|
2879
|
+
const f = m.frame;
|
|
2880
|
+
return s * face(f.axisX + s * f.ears.attachAt(y), y, 30 * s, 0)[0];
|
|
2881
|
+
}
|
|
2882
|
+
function farEarCovered(m) {
|
|
2883
|
+
const ears = m.frame.ears;
|
|
2884
|
+
if (ears === void 0) return true;
|
|
2885
|
+
const face = faceField(m);
|
|
2886
|
+
const rim = TURN.earFar * m.face;
|
|
2887
|
+
const y0 = ears.bottom - 2;
|
|
2888
|
+
const y1 = ears.top + 2;
|
|
2889
|
+
const n = Math.max(1, Math.ceil(y1 - y0));
|
|
2890
|
+
for (let i = 0; i <= n; i++) {
|
|
2891
|
+
const y = y0 + (y1 - y0) * i / n;
|
|
2892
|
+
for (const s of [-1, 1]) if (earRoot(m, face, y, s) < rim) return false;
|
|
2893
|
+
}
|
|
2894
|
+
return true;
|
|
2895
|
+
}
|
|
2830
2896
|
function chinSlide(m) {
|
|
2831
2897
|
const f = m.frame;
|
|
2832
2898
|
return Math.abs(faceField(m)(f.axisX, f.chinY, 30, 0)[0]);
|
|
@@ -2969,14 +3035,19 @@ function hairFrontField(m) {
|
|
|
2969
3035
|
);
|
|
2970
3036
|
return core + (m.hairOuter - core) * out;
|
|
2971
3037
|
};
|
|
2972
|
-
const ride = outerRide(m, inner, coreAt, followAt);
|
|
2973
|
-
const followOf = (x, y, s) => followAt(x, y, s) + (ride === void 0 ? 0 : ride(x, y, s));
|
|
3038
|
+
const ride = outerRide(m, inner, coreAt, followAt, eyeY + 1.5 * eyeH);
|
|
3039
|
+
const followOf = (x, y, s) => followAt(x, y, s) + (ride === void 0 ? 0 : ride.lift(x, y, s));
|
|
2974
3040
|
const drift = Math.abs(m.hairBack);
|
|
2975
3041
|
const crown = crownRide(m, cap, (x, y, s) => followOf(x, y, s) + drift);
|
|
3042
|
+
const boundOf = (x, y, s) => {
|
|
3043
|
+
if (ride === void 0) return followOf(x, y, s);
|
|
3044
|
+
const b = ride.blend(y, s);
|
|
3045
|
+
return (1 - b) * (followOf(x, y, s) + ride.cut(x, y, s)) - b * drift;
|
|
3046
|
+
};
|
|
2976
3047
|
return (x, y, ax, ay) => {
|
|
2977
3048
|
const s = Math.sign(ax);
|
|
2978
3049
|
const a = Math.abs(ax) / 30;
|
|
2979
|
-
const rx = s * a *
|
|
3050
|
+
const rx = s * a * boundOf(x, y, s) + a * (m.shellScale - 1) * (x - f.axisX);
|
|
2980
3051
|
const ry = nodDy(NOD.hairFront, ay, f.hh);
|
|
2981
3052
|
const bx = back(x, y, ax, ay)[0];
|
|
2982
3053
|
const by = nodDy(cap, ay, f.hh);
|
|
@@ -2996,6 +3067,18 @@ function gridRow(ys, y, snap = false) {
|
|
|
2996
3067
|
const r = Math.min(n - 2, Math.floor(v));
|
|
2997
3068
|
return { r, t: v - r };
|
|
2998
3069
|
}
|
|
3070
|
+
function gridCol(xs, x) {
|
|
3071
|
+
let c = 0;
|
|
3072
|
+
while (c < xs.length - 2 && xs[c + 1] < x) c++;
|
|
3073
|
+
return { c, t: clamp((x - xs[c]) / (xs[c + 1] - xs[c]), 0, 1) };
|
|
3074
|
+
}
|
|
3075
|
+
function rowWindow(ys, r, limit) {
|
|
3076
|
+
const n = ys.length;
|
|
3077
|
+
return [
|
|
3078
|
+
Math.max(0, Math.floor(ys[0] - ys[Math.max(0, r - 1)])),
|
|
3079
|
+
Math.min(limit, Math.ceil(ys[0] - ys[Math.min(n - 1, r + 1)]))
|
|
3080
|
+
];
|
|
3081
|
+
}
|
|
2999
3082
|
function renderedCrownBlend(m, ys) {
|
|
3000
3083
|
const wr = ys.map((y) => crownBlend(m, y));
|
|
3001
3084
|
return {
|
|
@@ -3071,10 +3154,11 @@ function paints(runs, px) {
|
|
|
3071
3154
|
}
|
|
3072
3155
|
return false;
|
|
3073
3156
|
}
|
|
3074
|
-
function outerRide(m, inner, coreAt, followAt) {
|
|
3157
|
+
function outerRide(m, inner, coreAt, followAt, capLimit) {
|
|
3075
3158
|
const f = m.frame;
|
|
3076
|
-
const
|
|
3077
|
-
if (
|
|
3159
|
+
const grid = m.hairFrontGrid;
|
|
3160
|
+
if (grid === void 0) return void 0;
|
|
3161
|
+
const { xs, ys } = grid;
|
|
3078
3162
|
const top = ys[0];
|
|
3079
3163
|
if (f.hairRuns("hair_front", top) === void 0 || f.hairRuns("hair_back", top) === void 0) {
|
|
3080
3164
|
return void 0;
|
|
@@ -3083,6 +3167,9 @@ function outerRide(m, inner, coreAt, followAt) {
|
|
|
3083
3167
|
const sides = [-1, 1];
|
|
3084
3168
|
const n = ys.length;
|
|
3085
3169
|
const count = Math.round(top - ys[n - 1]);
|
|
3170
|
+
let last = 0;
|
|
3171
|
+
while (last + 1 < n && ys[last + 1] >= capLimit) last++;
|
|
3172
|
+
const capRows = last === 0 ? 0 : Math.min(count, Math.floor(top - ys[last] - 0.5) + 1);
|
|
3086
3173
|
const below = (NOD.hairFront.down - NOD.hairBack.down) * f.hh;
|
|
3087
3174
|
const above = (NOD.hairBack.up - NOD.hairFront.up) * f.hh;
|
|
3088
3175
|
const j0 = Math.floor(-above);
|
|
@@ -3091,8 +3178,9 @@ function outerRide(m, inner, coreAt, followAt) {
|
|
|
3091
3178
|
(_, j) => f.hairRuns("hair_back", top - (j + j0) - 0.5)
|
|
3092
3179
|
);
|
|
3093
3180
|
const offset = sides.map(() => new Float64Array(count).fill(NaN));
|
|
3094
|
-
const
|
|
3095
|
-
const
|
|
3181
|
+
const reachOut = sides.map(() => new Float64Array(count));
|
|
3182
|
+
const reachIn = sides.map(() => new Float64Array(count));
|
|
3183
|
+
const bound = sides.map(() => new Uint8Array(count));
|
|
3096
3184
|
for (let i = 0; i < count; i++) {
|
|
3097
3185
|
const y = top - i - 0.5;
|
|
3098
3186
|
const front = f.hairRuns("hair_front", y);
|
|
@@ -3107,24 +3195,25 @@ function outerRide(m, inner, coreAt, followAt) {
|
|
|
3107
3195
|
inward = Math.min(inward, c.inward);
|
|
3108
3196
|
}
|
|
3109
3197
|
offset[k][i] = side * (edge - f.axisX);
|
|
3110
|
-
|
|
3111
|
-
|
|
3198
|
+
reachOut[k][i] = outward;
|
|
3199
|
+
reachIn[k][i] = inward;
|
|
3200
|
+
if (i < capRows && runReach(backRows[i - j0], edge, side).inward > 0)
|
|
3201
|
+
bound[k][i] = 1;
|
|
3112
3202
|
});
|
|
3113
3203
|
}
|
|
3114
3204
|
const end = new Float64Array(n * 2);
|
|
3115
3205
|
const lift = new Float64Array(n * 4);
|
|
3116
3206
|
for (let r = 0; r < n; r++) {
|
|
3117
|
-
const i0 =
|
|
3118
|
-
const i1 = Math.min(count, Math.ceil(top - ys[Math.min(n - 1, r + 1)]));
|
|
3207
|
+
const [i0, i1] = rowWindow(ys, r, count);
|
|
3119
3208
|
sides.forEach((side, k) => {
|
|
3120
3209
|
let d = Infinity;
|
|
3121
|
-
let
|
|
3122
|
-
let
|
|
3210
|
+
let outward = Infinity;
|
|
3211
|
+
let inward = Infinity;
|
|
3123
3212
|
for (let i = i0; i < i1; i++) {
|
|
3124
3213
|
if (Number.isNaN(offset[k][i])) continue;
|
|
3125
3214
|
d = Math.min(d, offset[k][i]);
|
|
3126
|
-
|
|
3127
|
-
|
|
3215
|
+
outward = Math.min(outward, reachOut[k][i]);
|
|
3216
|
+
inward = Math.min(inward, reachIn[k][i]);
|
|
3128
3217
|
}
|
|
3129
3218
|
end[r * 2 + k] = d;
|
|
3130
3219
|
if (d === Infinity || d <= inner) return;
|
|
@@ -3132,7 +3221,7 @@ function outerRide(m, inner, coreAt, followAt) {
|
|
|
3132
3221
|
sides.forEach((s, j) => {
|
|
3133
3222
|
const allow = Math.min(
|
|
3134
3223
|
coreAt(x, ys[r], s),
|
|
3135
|
-
s === side ?
|
|
3224
|
+
m.shellScale * (s === side ? outward : inward) - drift
|
|
3136
3225
|
);
|
|
3137
3226
|
lift[(r * 2 + k) * 2 + j] = Math.max(0, allow - followAt(x, ys[r], s));
|
|
3138
3227
|
});
|
|
@@ -3142,7 +3231,7 @@ function outerRide(m, inner, coreAt, followAt) {
|
|
|
3142
3231
|
const v = lift[(r * 2 + k) * 2 + j];
|
|
3143
3232
|
return v === 0 ? 0 : v * Math.min(1, (d - inner) / (end[r * 2 + k] - inner));
|
|
3144
3233
|
};
|
|
3145
|
-
|
|
3234
|
+
const liftAt = (x, y, s) => {
|
|
3146
3235
|
const d = Math.abs(x - f.axisX);
|
|
3147
3236
|
if (s === 0 || d <= inner) return 0;
|
|
3148
3237
|
const k = x < f.axisX ? 0 : 1;
|
|
@@ -3150,6 +3239,130 @@ function outerRide(m, inner, coreAt, followAt) {
|
|
|
3150
3239
|
const { r, t } = gridRow(ys, y, true);
|
|
3151
3240
|
return rowRide(r, k, j, d) * (1 - t) + rowRide(r + 1, k, j, d) * t;
|
|
3152
3241
|
};
|
|
3242
|
+
const nc = xs.length;
|
|
3243
|
+
const along = (v, x) => {
|
|
3244
|
+
const { c, t } = gridCol(xs, x);
|
|
3245
|
+
return v[c] + (v[c + 1] - v[c]) * t;
|
|
3246
|
+
};
|
|
3247
|
+
const readAt = (r, x, y) => {
|
|
3248
|
+
const { c, t: fx } = gridCol(xs, x);
|
|
3249
|
+
const dx = xs[c + 1] - xs[c];
|
|
3250
|
+
if (r + 1 < n && y <= ys[r] && y >= ys[r + 1]) {
|
|
3251
|
+
const fy = (ys[r] - y) / (ys[r] - ys[r + 1]);
|
|
3252
|
+
if (fy >= 1) return void 0;
|
|
3253
|
+
return fx <= fy ? xs[c] : xs[c] + (fx - fy) / (1 - fy) * dx;
|
|
3254
|
+
}
|
|
3255
|
+
if (r > 0 && y > ys[r] && y < ys[r - 1]) {
|
|
3256
|
+
const fy = (ys[r - 1] - y) / (ys[r - 1] - ys[r]);
|
|
3257
|
+
return fx <= fy ? xs[c] + fx / fy * dx : xs[c + 1];
|
|
3258
|
+
}
|
|
3259
|
+
return void 0;
|
|
3260
|
+
};
|
|
3261
|
+
const ahead = (e, s) => s === e.side ? e.outward : e.inward;
|
|
3262
|
+
const behind = (e, s) => s === e.side ? e.inward : e.outward;
|
|
3263
|
+
const capCut = (r, s, side, today, mine) => {
|
|
3264
|
+
const reach = Math.max(...mine.map((e) => side * (e.x - f.axisX)));
|
|
3265
|
+
const quarter = new Float64Array(nc);
|
|
3266
|
+
const cols = xs.map((_, c) => c).filter((c) => (xs[c] < f.axisX ? -1 : 1) === side);
|
|
3267
|
+
if (side < 0) cols.reverse();
|
|
3268
|
+
let total = 0;
|
|
3269
|
+
let x0 = f.axisX;
|
|
3270
|
+
let d0 = s * followAt(f.axisX, ys[r], s);
|
|
3271
|
+
let open = true;
|
|
3272
|
+
for (const c of cols) {
|
|
3273
|
+
if (open) {
|
|
3274
|
+
const d = (m.shellScale - 1) * (xs[c] - f.axisX) + s * today[c];
|
|
3275
|
+
const rest = side * (xs[c] - x0);
|
|
3276
|
+
const full = rest + side * (d - d0);
|
|
3277
|
+
total += Math.max(0, Math.min(full, rest)) / 4;
|
|
3278
|
+
x0 = xs[c];
|
|
3279
|
+
d0 = d;
|
|
3280
|
+
open = side * (xs[c] - f.axisX) < reach;
|
|
3281
|
+
}
|
|
3282
|
+
quarter[c] = total;
|
|
3283
|
+
}
|
|
3284
|
+
let scale = 0;
|
|
3285
|
+
for (const e of mine) {
|
|
3286
|
+
const need = along(today, e.at) - (m.shellScale * ahead(e, s) - drift);
|
|
3287
|
+
if (need <= 0) continue;
|
|
3288
|
+
const q = along(quarter, e.at);
|
|
3289
|
+
scale = Math.max(scale, q > 0 ? need / q : 1);
|
|
3290
|
+
}
|
|
3291
|
+
scale = Math.min(1, scale);
|
|
3292
|
+
return quarter.map((q) => scale * q);
|
|
3293
|
+
};
|
|
3294
|
+
const cuts = new Float64Array(n * 2 * nc);
|
|
3295
|
+
const blend = new Float64Array(n * 2);
|
|
3296
|
+
const wr = ys.map((y) => crownBlend(m, y));
|
|
3297
|
+
for (let r = 0; r <= last && capRows > 0; r++) {
|
|
3298
|
+
const w = wr[r];
|
|
3299
|
+
if (w <= 0) continue;
|
|
3300
|
+
const edges = [];
|
|
3301
|
+
const [i0, i1] = rowWindow(ys, r, capRows);
|
|
3302
|
+
for (let i = i0; i < i1; i++) {
|
|
3303
|
+
sides.forEach((side, k) => {
|
|
3304
|
+
if (bound[k][i] === 0) return;
|
|
3305
|
+
const x = f.axisX + side * offset[k][i];
|
|
3306
|
+
const at = readAt(r, x, top - i - 0.5);
|
|
3307
|
+
if (at === void 0) return;
|
|
3308
|
+
edges.push({
|
|
3309
|
+
side,
|
|
3310
|
+
x,
|
|
3311
|
+
at,
|
|
3312
|
+
outward: reachOut[k][i],
|
|
3313
|
+
inward: reachIn[k][i]
|
|
3314
|
+
});
|
|
3315
|
+
});
|
|
3316
|
+
}
|
|
3317
|
+
if (edges.length === 0) continue;
|
|
3318
|
+
sides.forEach((s, j) => {
|
|
3319
|
+
const today = xs.map((x) => followAt(x, ys[r], s) + liftAt(x, ys[r], s));
|
|
3320
|
+
const after = today.slice();
|
|
3321
|
+
if (w >= 1) {
|
|
3322
|
+
for (const side of sides) {
|
|
3323
|
+
const mine = edges.filter((e) => e.side === side);
|
|
3324
|
+
if (mine.length === 0) continue;
|
|
3325
|
+
const cut = capCut(r, s, side, today, mine);
|
|
3326
|
+
for (let c = 0; c < nc; c++) after[c] -= cut[c];
|
|
3327
|
+
}
|
|
3328
|
+
}
|
|
3329
|
+
let b = 0;
|
|
3330
|
+
for (const e of edges) {
|
|
3331
|
+
const rel = w * (along(after, e.at) + drift);
|
|
3332
|
+
const hi = m.shellScale * ahead(e, s);
|
|
3333
|
+
const lo = -m.shellScale * behind(e, s);
|
|
3334
|
+
if (rel > hi) b = Math.max(b, 1 - hi / rel);
|
|
3335
|
+
else if (rel < lo) b = Math.max(b, 1 - lo / rel);
|
|
3336
|
+
}
|
|
3337
|
+
blend[r * 2 + j] = b;
|
|
3338
|
+
for (let c = 0; c < nc; c++)
|
|
3339
|
+
cuts[(r * 2 + j) * nc + c] = after[c] - today[c];
|
|
3340
|
+
});
|
|
3341
|
+
}
|
|
3342
|
+
for (let j = 0; j < 2; j++) {
|
|
3343
|
+
for (let r = n - 2; r >= 0; r--) {
|
|
3344
|
+
if (wr[r] < 1)
|
|
3345
|
+
blend[r * 2 + j] = Math.max(blend[r * 2 + j], blend[(r + 1) * 2 + j]);
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
const cutRow = (r, j, x) => r > last ? 0 : along(cuts.subarray((r * 2 + j) * nc, (r * 2 + j + 1) * nc), x);
|
|
3349
|
+
return {
|
|
3350
|
+
lift: liftAt,
|
|
3351
|
+
// Between vertex rows, as the render interpolates; a vertex reads its own
|
|
3352
|
+
// row's alone.
|
|
3353
|
+
cut: (x, y, s) => {
|
|
3354
|
+
if (s === 0) return 0;
|
|
3355
|
+
const j = s < 0 ? 0 : 1;
|
|
3356
|
+
const { r, t } = gridRow(ys, y, true);
|
|
3357
|
+
return cutRow(r, j, x) * (1 - t) + cutRow(r + 1, j, x) * t;
|
|
3358
|
+
},
|
|
3359
|
+
blend: (y, s) => {
|
|
3360
|
+
if (s === 0) return 0;
|
|
3361
|
+
const j = s < 0 ? 0 : 1;
|
|
3362
|
+
const { r, t } = gridRow(ys, y, true);
|
|
3363
|
+
return blend[r * 2 + j] * (1 - t) + blend[(r + 1) * 2 + j] * t;
|
|
3364
|
+
}
|
|
3365
|
+
};
|
|
3153
3366
|
}
|
|
3154
3367
|
function crownRide(m, cap, relAt) {
|
|
3155
3368
|
const f = m.frame;
|
|
@@ -3189,8 +3402,7 @@ function crownRide(m, cap, relAt) {
|
|
|
3189
3402
|
return rel[at];
|
|
3190
3403
|
};
|
|
3191
3404
|
const cellR = (x, y, k) => {
|
|
3192
|
-
|
|
3193
|
-
while (c < xs.length - 2 && xs[c + 1] < x) c++;
|
|
3405
|
+
const { c } = gridCol(xs, x);
|
|
3194
3406
|
const { r } = gridRow(ys, y);
|
|
3195
3407
|
return Math.max(
|
|
3196
3408
|
relOf(r, c, k),
|
|
@@ -3280,8 +3492,7 @@ function crownRide(m, cap, relAt) {
|
|
|
3280
3492
|
const out = wr.slice();
|
|
3281
3493
|
for (let r = 0; r < n; r++) {
|
|
3282
3494
|
if (wr[r] >= 1) continue;
|
|
3283
|
-
const i0 =
|
|
3284
|
-
const i1 = Math.min(count, Math.ceil(top - ys[Math.min(n - 1, r + 1)]));
|
|
3495
|
+
const [i0, i1] = rowWindow(ys, r, count);
|
|
3285
3496
|
let least = Infinity;
|
|
3286
3497
|
for (let i = i0; i < i1; i++) least = Math.min(least, slackAt(i, k));
|
|
3287
3498
|
out[r] = wr[r] + clamp(least, 0, 1 - wr[r]);
|
|
@@ -3950,6 +4161,18 @@ function fitTurn(ctx, targets, style, build) {
|
|
|
3950
4161
|
}
|
|
3951
4162
|
p.shell = bisect((k) => at(k) - s, SHELL_RANGE[0], SHELL_RANGE[1]);
|
|
3952
4163
|
}
|
|
4164
|
+
const covered = (shell) => farEarCovered(build({ ...p, shell }));
|
|
4165
|
+
if (!covered(p.shell) && covered(Math.max(1, p.shell))) {
|
|
4166
|
+
let lo = p.shell;
|
|
4167
|
+
let hi = Math.max(1, p.shell);
|
|
4168
|
+
for (let i = 0; i < 50; i++) {
|
|
4169
|
+
const mid = (lo + hi) / 2;
|
|
4170
|
+
if (covered(mid)) hi = mid;
|
|
4171
|
+
else lo = mid;
|
|
4172
|
+
}
|
|
4173
|
+
p.shell = hi;
|
|
4174
|
+
clamped.push("silhouetteRatio");
|
|
4175
|
+
}
|
|
3953
4176
|
const hb = ctx.restHalf;
|
|
3954
4177
|
const feature = (key, family, at, box) => {
|
|
3955
4178
|
const base = build(p);
|
|
@@ -4108,6 +4331,7 @@ function buildTurn(frame, landmarks, q) {
|
|
|
4108
4331
|
hairOuter: q.outline * face,
|
|
4109
4332
|
hairBack: A * hh * TURN.hairBack,
|
|
4110
4333
|
shellScale: q.shell,
|
|
4334
|
+
earFarScale: 1 + A * (TURN.earFarScale - 1),
|
|
4111
4335
|
...landmarks
|
|
4112
4336
|
};
|
|
4113
4337
|
}
|