@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.js
CHANGED
|
@@ -1963,14 +1963,155 @@ function createDefaultWarpDeformer(model) {
|
|
|
1963
1963
|
};
|
|
1964
1964
|
}
|
|
1965
1965
|
|
|
1966
|
+
// src/auto-rig/profile.ts
|
|
1967
|
+
var PROFILE_VALUES = {
|
|
1968
|
+
slide: 0.12,
|
|
1969
|
+
lead: 0.1,
|
|
1970
|
+
noseDepth: 2,
|
|
1971
|
+
hairFollow: 1,
|
|
1972
|
+
fore: 0.2,
|
|
1973
|
+
backShare: 0.1,
|
|
1974
|
+
earFar: 0.5,
|
|
1975
|
+
nodDown: 0.18,
|
|
1976
|
+
nodUp: 0.1,
|
|
1977
|
+
nodLead: 0.06,
|
|
1978
|
+
chinShare: 0.75,
|
|
1979
|
+
rollDeg: 14,
|
|
1980
|
+
gaze: 0.2,
|
|
1981
|
+
blink: 0.6,
|
|
1982
|
+
mouthOpen: 0.8,
|
|
1983
|
+
mouthWiden: 1.2,
|
|
1984
|
+
brow: 0.15,
|
|
1985
|
+
breath: 0.03,
|
|
1986
|
+
sway: 0.08
|
|
1987
|
+
};
|
|
1988
|
+
function deriveProfile(v) {
|
|
1989
|
+
const farLead = v.lead * (1 - v.fore / 2);
|
|
1990
|
+
const nearLead = v.lead * (1 + v.fore / 2);
|
|
1991
|
+
const turn = {
|
|
1992
|
+
/** The face plate's translation. */
|
|
1993
|
+
face: v.slide,
|
|
1994
|
+
/** The chin tip's lead over the plate. */
|
|
1995
|
+
chinLead: v.lead,
|
|
1996
|
+
/** The plate's width at full turn: eye and cheek rows, then the jaw. The
|
|
1997
|
+
* plate translates; it does not narrow. */
|
|
1998
|
+
widthUpper: 1,
|
|
1999
|
+
widthJaw: 1,
|
|
2000
|
+
eyeFar: v.slide + farLead,
|
|
2001
|
+
eyeNear: v.slide + nearLead,
|
|
2002
|
+
/** Each eye's width at full turn, about its own centre. */
|
|
2003
|
+
eyeFarScale: 1 - v.fore,
|
|
2004
|
+
eyeNearScale: 1 + v.fore / 2,
|
|
2005
|
+
browFar: v.slide + farLead,
|
|
2006
|
+
browNear: v.slide + nearLead,
|
|
2007
|
+
nose: v.slide + v.noseDepth * v.lead,
|
|
2008
|
+
mouth: v.slide + v.lead,
|
|
2009
|
+
mouthWidth: 1,
|
|
2010
|
+
/** The mouth's far corner rises (its near end drops) by this much: it
|
|
2011
|
+
* stays level. */
|
|
2012
|
+
mouthTiltDeg: 0,
|
|
2013
|
+
/** The front hair (bangs and side locks) rides the face at this share of
|
|
2014
|
+
* its translation. */
|
|
2015
|
+
hairFollow: v.hairFollow,
|
|
2016
|
+
/** The head's outline at the eye row holds: where the front hair draws
|
|
2017
|
+
* it, its outer edge follows the face this much on a row where no back
|
|
2018
|
+
* hair paints behind that edge. Where back hair does, the edge rides
|
|
2019
|
+
* further, as far as that back hair stays behind it through the turn and
|
|
2020
|
+
* the nod, up to the face's follow there (`hairFollow`; over the far
|
|
2021
|
+
* eye's rows, as far as that eye's outer corner goes). On the cap (the
|
|
2022
|
+
* rows above the eyes) it follows less where the turn would otherwise
|
|
2023
|
+
* carry it further outside its back hair than the nod alone does. */
|
|
2024
|
+
outlineFollow: 0,
|
|
2025
|
+
/** The back hair's slight counter-motion. */
|
|
2026
|
+
hairBack: -v.backShare * v.slide,
|
|
2027
|
+
/** The ears lag the plate: the far one's outer edge moves this share of
|
|
2028
|
+
* its slide; the near one rides the head. */
|
|
2029
|
+
earFar: v.earFar,
|
|
2030
|
+
earNear: 1,
|
|
2031
|
+
/** The far ear's width at full turn, about its outer edge, where the head
|
|
2032
|
+
* leaves it room; less where it does not. */
|
|
2033
|
+
earFarScale: 1 - v.fore
|
|
2034
|
+
};
|
|
2035
|
+
const mid = v.nodDown * (1 + v.chinShare) / 2;
|
|
2036
|
+
const feature = (depth) => ({
|
|
2037
|
+
up: -(v.nodUp + depth * v.nodLead),
|
|
2038
|
+
down: mid + depth * v.nodLead
|
|
2039
|
+
});
|
|
2040
|
+
const nod = {
|
|
2041
|
+
faceTop: { up: -v.nodUp, down: v.nodDown },
|
|
2042
|
+
chin: { up: -v.nodUp, down: v.chinShare * v.nodDown },
|
|
2043
|
+
eye: feature(1),
|
|
2044
|
+
/** Eye height at the extreme, about the eye's centre: it holds. */
|
|
2045
|
+
eyeHeight: { up: 1, down: 1 },
|
|
2046
|
+
brow: feature(1),
|
|
2047
|
+
nose: feature(v.noseDepth),
|
|
2048
|
+
mouth: feature(1),
|
|
2049
|
+
hairFront: { up: -v.hairFollow * v.nodUp, down: v.hairFollow * v.nodDown },
|
|
2050
|
+
/** The front hair's cap top: half the plate's rise looking up; looking
|
|
2051
|
+
* down the plate's top, which the rig slides only as far as the back
|
|
2052
|
+
* hair covers the crown behind it. */
|
|
2053
|
+
hairFrontTop: { up: -v.nodUp / 2, down: v.nodDown },
|
|
2054
|
+
hairBack: { up: -v.backShare * v.nodUp, down: v.backShare * v.nodDown }
|
|
2055
|
+
};
|
|
2056
|
+
const amplitude = {
|
|
2057
|
+
/** Iris travel, in iris widths. */
|
|
2058
|
+
gazeX: v.gaze,
|
|
2059
|
+
gazeY: v.gaze / 2,
|
|
2060
|
+
/** How far the upper lid comes down, over the eye's height. */
|
|
2061
|
+
blink: v.blink,
|
|
2062
|
+
/** A single mouth drawing opens to this height over its rest width. */
|
|
2063
|
+
mouthOpenHeight: v.mouthOpen,
|
|
2064
|
+
/** The mouth widens this much open. */
|
|
2065
|
+
mouthOpenWidth: v.mouthWiden,
|
|
2066
|
+
/** Brow raise / lower, hh. */
|
|
2067
|
+
brow: v.brow,
|
|
2068
|
+
/** Breath lifts the shoulders and the head, hh. */
|
|
2069
|
+
breathBody: v.breath,
|
|
2070
|
+
breathHead: v.breath * 2 / 3,
|
|
2071
|
+
/** Hair tip travel at full sway (±20), hh. */
|
|
2072
|
+
sway: v.sway,
|
|
2073
|
+
/** The neck under the face reaches this far above the jaw, hh — what the
|
|
2074
|
+
* chin uncovers as it slides off it: half again the chin's full-turn
|
|
2075
|
+
* slide, to the next 0.05. */
|
|
2076
|
+
hiddenNeck: Math.ceil(30 * (v.slide + v.lead) - 1e-9) / 20
|
|
2077
|
+
};
|
|
2078
|
+
return { TURN: turn, NOD: nod, ROLL_DEG: v.rollDeg, AMPLITUDE: amplitude };
|
|
2079
|
+
}
|
|
2080
|
+
var { TURN, NOD, ROLL_DEG, AMPLITUDE } = deriveProfile(PROFILE_VALUES);
|
|
2081
|
+
var STYLE_KNOBS = [
|
|
2082
|
+
"turn",
|
|
2083
|
+
"featureLead",
|
|
2084
|
+
"hairFollow",
|
|
2085
|
+
"outlineFollow",
|
|
2086
|
+
"blink",
|
|
2087
|
+
"sway"
|
|
2088
|
+
];
|
|
2089
|
+
function resolveStyle(style, check) {
|
|
2090
|
+
const s = style ?? {};
|
|
2091
|
+
const get = (key, def, lo, hi) => s[key] === void 0 ? def : check(`style.${key}`, s[key], lo, hi);
|
|
2092
|
+
return {
|
|
2093
|
+
turn: get("turn", 1, 0, 3),
|
|
2094
|
+
featureLead: get("featureLead", 1, 0, 3),
|
|
2095
|
+
hairFollow: get("hairFollow", TURN.hairFollow, 0, 2),
|
|
2096
|
+
outlineFollow: get("outlineFollow", TURN.outlineFollow, 0, 2),
|
|
2097
|
+
blink: get("blink", AMPLITUDE.blink, 0.1, 1),
|
|
2098
|
+
sway: get("sway", 1, 0, 5)
|
|
2099
|
+
};
|
|
2100
|
+
}
|
|
2101
|
+
|
|
1966
2102
|
// src/auto-rig/types.ts
|
|
2103
|
+
var round2 = (v) => Math.round(v * 100) / 100;
|
|
1967
2104
|
var TurnTargetError = class extends Error {
|
|
1968
2105
|
constructor(message) {
|
|
1969
2106
|
super(message);
|
|
1970
2107
|
this.name = "TurnTargetError";
|
|
1971
2108
|
}
|
|
1972
2109
|
};
|
|
1973
|
-
var DEFAULT_TURN_TARGETS = Object.freeze({
|
|
2110
|
+
var DEFAULT_TURN_TARGETS = Object.freeze({
|
|
2111
|
+
eyeShift: round2((TURN.eyeFar + TURN.eyeNear) / 2 - TURN.hairBack),
|
|
2112
|
+
farEyeRatio: round2(TURN.eyeFarScale / TURN.eyeNearScale),
|
|
2113
|
+
silhouetteRatio: 1
|
|
2114
|
+
});
|
|
1974
2115
|
|
|
1975
2116
|
// src/auto-rig/roles.ts
|
|
1976
2117
|
var ROLE_TABLE = [
|
|
@@ -2427,110 +2568,6 @@ function land(l, offsets, x, y) {
|
|
|
2427
2568
|
return [topX + (botX - topX) * t, topY + (botY - topY) * t];
|
|
2428
2569
|
}
|
|
2429
2570
|
|
|
2430
|
-
// src/auto-rig/profile.ts
|
|
2431
|
-
var HH_PER_EYE_TO_CHIN = 1.017;
|
|
2432
|
-
var TURN = {
|
|
2433
|
-
/** The face plate's translation (its cheek edges, 0.118 / 0.126). */
|
|
2434
|
-
face: 0.118,
|
|
2435
|
-
/** The chin tip's lead over the plate (it moves 0.194). */
|
|
2436
|
-
chinLead: 0.076,
|
|
2437
|
-
/** The plate's width at full turn: eye and cheek rows, then the jaw. */
|
|
2438
|
-
widthUpper: 0.985,
|
|
2439
|
-
widthJaw: 0.96,
|
|
2440
|
-
eyeFar: 0.192,
|
|
2441
|
-
eyeNear: 0.23,
|
|
2442
|
-
/** Each eye's width at full turn, about its own centre. */
|
|
2443
|
-
eyeFarScale: 0.85,
|
|
2444
|
-
eyeNearScale: 1.085,
|
|
2445
|
-
browFar: 0.213,
|
|
2446
|
-
browNear: 0.243,
|
|
2447
|
-
nose: 0.301,
|
|
2448
|
-
mouth: 0.213,
|
|
2449
|
-
mouthWidth: 0.975,
|
|
2450
|
-
/** The mouth's far corner rises (its near end drops) by this much. */
|
|
2451
|
-
mouthTiltDeg: 4.4,
|
|
2452
|
-
/** The front hair (bangs and side locks) rides the face at this share of
|
|
2453
|
-
* its translation (bangs 1.18×, side locks at the eye row ≈ 1.0×). */
|
|
2454
|
-
hairFollow: 1.1,
|
|
2455
|
-
/** The head's outline at the eye row holds (−0.014 / −0.004 hh): where the
|
|
2456
|
-
* front hair draws it, its outer edge follows the face this much on a row
|
|
2457
|
-
* where no back hair paints behind that edge. Where back hair does, the
|
|
2458
|
-
* edge rides further, as far as that back hair stays behind it through
|
|
2459
|
-
* the turn and the nod, up to the face's follow there (`hairFollow`;
|
|
2460
|
-
* over the far eye's rows, as far as that eye's outer corner goes). */
|
|
2461
|
-
outlineFollow: 0,
|
|
2462
|
-
/** The back hair's slight counter-motion. */
|
|
2463
|
-
hairBack: -0.027,
|
|
2464
|
-
/** The ears lag the plate: the far one moves this share of its slide, the
|
|
2465
|
-
* near one this at its widest reach, its root riding the head (the
|
|
2466
|
-
* samples' parallax ratios, 0.44 and 0.87). */
|
|
2467
|
-
earFar: 0.44,
|
|
2468
|
-
earNear: 0.87
|
|
2469
|
-
};
|
|
2470
|
-
var NOD = {
|
|
2471
|
-
faceTop: { up: -0.081, down: 0.178 },
|
|
2472
|
-
chin: { up: -0.0875, down: 0.109 },
|
|
2473
|
-
eye: { up: -0.167, down: 0.202 },
|
|
2474
|
-
/** Eye height at the extreme, about the eye's centre. */
|
|
2475
|
-
eyeHeight: { up: 0.994, down: 0.96 },
|
|
2476
|
-
brow: { up: -0.167, down: 0.223 },
|
|
2477
|
-
nose: { up: -0.207, down: 0.193 },
|
|
2478
|
-
mouth: { up: -0.172, down: 0.177 },
|
|
2479
|
-
hairFront: { up: -0.11, down: 0.189 },
|
|
2480
|
-
/** The front hair's cap top. The samples' cap top moves 1.215× their face's
|
|
2481
|
-
* centroid looking down (Mao 1.236, Haru 1.626, Hiyori 1.194, Natori
|
|
2482
|
-
* 0.730) and 0.415× looking up (0.385, 0.430, 0.400, 0.487); each ratio
|
|
2483
|
-
* times the plate's own nod halfway between its top and the chin (0.1435
|
|
2484
|
-
* down, 0.084 up). Looking down, the rig slides it only as far as the
|
|
2485
|
-
* back hair covers the crown behind it. */
|
|
2486
|
-
hairFrontTop: { up: -0.035, down: 0.174 },
|
|
2487
|
-
hairBack: { up: -0.011, down: 0.025 }
|
|
2488
|
-
};
|
|
2489
|
-
var ROLL_DEG = 10;
|
|
2490
|
-
var AMPLITUDE = {
|
|
2491
|
-
/** Iris travel, in iris widths (X 0.045 hh, Y 0.031 hh on the samples). */
|
|
2492
|
-
gazeX: 0.17,
|
|
2493
|
-
gazeY: 0.11,
|
|
2494
|
-
/** How far the upper lid comes down, over the eye's height (Haru 0.30,
|
|
2495
|
-
* Mao 0.58, Hiyori 0.67). */
|
|
2496
|
-
blink: 0.58,
|
|
2497
|
-
/** A single mouth drawing opens to this height over its rest width. */
|
|
2498
|
-
mouthOpenHeight: 0.77,
|
|
2499
|
-
/** The mouth widens this much open. */
|
|
2500
|
-
mouthOpenWidth: 1.15,
|
|
2501
|
-
/** Brow raise / lower, hh. */
|
|
2502
|
-
brow: 0.089,
|
|
2503
|
-
/** Breath lifts the shoulders and the head, hh. */
|
|
2504
|
-
breathBody: 0.025,
|
|
2505
|
-
breathHead: 0.016,
|
|
2506
|
-
/** Hair tip travel at full sway (±20), hh (the keyform ranges at ±1 have a
|
|
2507
|
-
* median of 0.05–0.06 hh). */
|
|
2508
|
-
sway: 0.06,
|
|
2509
|
-
/** The neck under the face reaches this far above the jaw, hh — what the
|
|
2510
|
-
* chin uncovers as it slides off it. */
|
|
2511
|
-
hiddenNeck: 0.3
|
|
2512
|
-
};
|
|
2513
|
-
var STYLE_KNOBS = [
|
|
2514
|
-
"turn",
|
|
2515
|
-
"featureLead",
|
|
2516
|
-
"hairFollow",
|
|
2517
|
-
"outlineFollow",
|
|
2518
|
-
"blink",
|
|
2519
|
-
"sway"
|
|
2520
|
-
];
|
|
2521
|
-
function resolveStyle(style, check) {
|
|
2522
|
-
const s = style ?? {};
|
|
2523
|
-
const get = (key, def, lo, hi) => s[key] === void 0 ? def : check(`style.${key}`, s[key], lo, hi);
|
|
2524
|
-
return {
|
|
2525
|
-
turn: get("turn", 1, 0, 3),
|
|
2526
|
-
featureLead: get("featureLead", 1, 0, 3),
|
|
2527
|
-
hairFollow: get("hairFollow", TURN.hairFollow, 0, 2),
|
|
2528
|
-
outlineFollow: get("outlineFollow", TURN.outlineFollow, 0, 2),
|
|
2529
|
-
blink: get("blink", AMPLITUDE.blink, 0.1, 1),
|
|
2530
|
-
sway: get("sway", 1, 0, 5)
|
|
2531
|
-
};
|
|
2532
|
-
}
|
|
2533
|
-
|
|
2534
2571
|
// src/auto-rig/head.ts
|
|
2535
2572
|
function outerEdge(runs, axisX, side) {
|
|
2536
2573
|
if (runs.length === 0) return void 0;
|
|
@@ -2743,7 +2780,7 @@ function buildHeadFrame(layers, opts) {
|
|
|
2743
2780
|
return face.y1;
|
|
2744
2781
|
};
|
|
2745
2782
|
const chin = chinY;
|
|
2746
|
-
const shade = JAW_SHADE *
|
|
2783
|
+
const shade = JAW_SHADE * Math.max(1, eyeY - chin);
|
|
2747
2784
|
const reach = opts.chinSlide === void 0 ? nk.waist : clamp(
|
|
2748
2785
|
nk.waist - opts.chinSlide - CUT_MARGIN - (CUT_LOW + 1),
|
|
2749
2786
|
0,
|
|
@@ -2792,7 +2829,7 @@ function buildHeadFrame(layers, opts) {
|
|
|
2792
2829
|
}
|
|
2793
2830
|
}
|
|
2794
2831
|
}
|
|
2795
|
-
const hh =
|
|
2832
|
+
const hh = Math.max(1, eyeY - chinY);
|
|
2796
2833
|
const headHalf = opts.headHalfWidth;
|
|
2797
2834
|
const hairShell = headHalf !== void 0;
|
|
2798
2835
|
let shellLeft = axisX - plateHalf;
|
|
@@ -2886,16 +2923,45 @@ function earField(m) {
|
|
|
2886
2923
|
const s = Math.sign(ax);
|
|
2887
2924
|
const a = Math.abs(ax) / 30;
|
|
2888
2925
|
const dy = face(x, y, 0, ay)[1];
|
|
2889
|
-
if (sideOf(m, x, s) === "far") return [s * a * TURN.earFar * m.face, dy];
|
|
2890
2926
|
const d = Math.abs(x - f.axisX);
|
|
2891
2927
|
const attach = ears.attachAt(y);
|
|
2928
|
+
const span = Math.max(1, ears.outer - attach);
|
|
2929
|
+
if (sideOf(m, x, s) === "far") {
|
|
2930
|
+
const rim2 = TURN.earFar * m.face;
|
|
2931
|
+
const root2 = earRoot(m, face, y, s);
|
|
2932
|
+
const lead = Math.max(
|
|
2933
|
+
0,
|
|
2934
|
+
Math.min((1 - m.earFarScale) * span, root2 - rim2)
|
|
2935
|
+
);
|
|
2936
|
+
let along = rim2 + lead * (ears.outer - d) / span;
|
|
2937
|
+
if (d < attach) along = Math.min(along, s * face(x, y, 30 * s, 0)[0]);
|
|
2938
|
+
return [s * a * along, dy];
|
|
2939
|
+
}
|
|
2892
2940
|
if (d <= attach) return [face(x, y, ax, 0)[0], dy];
|
|
2893
2941
|
const root = face(f.axisX + Math.sign(x - f.axisX) * attach, y, ax, 0)[0];
|
|
2894
2942
|
const rim = s * a * TURN.earNear * m.face;
|
|
2895
|
-
const t = Math.min(1, (d - attach) /
|
|
2943
|
+
const t = Math.min(1, (d - attach) / span);
|
|
2896
2944
|
return [root + (rim - root) * t, dy];
|
|
2897
2945
|
};
|
|
2898
2946
|
}
|
|
2947
|
+
function earRoot(m, face, y, s) {
|
|
2948
|
+
const f = m.frame;
|
|
2949
|
+
return s * face(f.axisX + s * f.ears.attachAt(y), y, 30 * s, 0)[0];
|
|
2950
|
+
}
|
|
2951
|
+
function farEarCovered(m) {
|
|
2952
|
+
const ears = m.frame.ears;
|
|
2953
|
+
if (ears === void 0) return true;
|
|
2954
|
+
const face = faceField(m);
|
|
2955
|
+
const rim = TURN.earFar * m.face;
|
|
2956
|
+
const y0 = ears.bottom - 2;
|
|
2957
|
+
const y1 = ears.top + 2;
|
|
2958
|
+
const n = Math.max(1, Math.ceil(y1 - y0));
|
|
2959
|
+
for (let i = 0; i <= n; i++) {
|
|
2960
|
+
const y = y0 + (y1 - y0) * i / n;
|
|
2961
|
+
for (const s of [-1, 1]) if (earRoot(m, face, y, s) < rim) return false;
|
|
2962
|
+
}
|
|
2963
|
+
return true;
|
|
2964
|
+
}
|
|
2899
2965
|
function chinSlide(m) {
|
|
2900
2966
|
const f = m.frame;
|
|
2901
2967
|
return Math.abs(faceField(m)(f.axisX, f.chinY, 30, 0)[0]);
|
|
@@ -3038,14 +3104,19 @@ function hairFrontField(m) {
|
|
|
3038
3104
|
);
|
|
3039
3105
|
return core + (m.hairOuter - core) * out;
|
|
3040
3106
|
};
|
|
3041
|
-
const ride = outerRide(m, inner, coreAt, followAt);
|
|
3042
|
-
const followOf = (x, y, s) => followAt(x, y, s) + (ride === void 0 ? 0 : ride(x, y, s));
|
|
3107
|
+
const ride = outerRide(m, inner, coreAt, followAt, eyeY + 1.5 * eyeH);
|
|
3108
|
+
const followOf = (x, y, s) => followAt(x, y, s) + (ride === void 0 ? 0 : ride.lift(x, y, s));
|
|
3043
3109
|
const drift = Math.abs(m.hairBack);
|
|
3044
3110
|
const crown = crownRide(m, cap, (x, y, s) => followOf(x, y, s) + drift);
|
|
3111
|
+
const boundOf = (x, y, s) => {
|
|
3112
|
+
if (ride === void 0) return followOf(x, y, s);
|
|
3113
|
+
const b = ride.blend(y, s);
|
|
3114
|
+
return (1 - b) * (followOf(x, y, s) + ride.cut(x, y, s)) - b * drift;
|
|
3115
|
+
};
|
|
3045
3116
|
return (x, y, ax, ay) => {
|
|
3046
3117
|
const s = Math.sign(ax);
|
|
3047
3118
|
const a = Math.abs(ax) / 30;
|
|
3048
|
-
const rx = s * a *
|
|
3119
|
+
const rx = s * a * boundOf(x, y, s) + a * (m.shellScale - 1) * (x - f.axisX);
|
|
3049
3120
|
const ry = nodDy(NOD.hairFront, ay, f.hh);
|
|
3050
3121
|
const bx = back(x, y, ax, ay)[0];
|
|
3051
3122
|
const by = nodDy(cap, ay, f.hh);
|
|
@@ -3065,6 +3136,18 @@ function gridRow(ys, y, snap = false) {
|
|
|
3065
3136
|
const r = Math.min(n - 2, Math.floor(v));
|
|
3066
3137
|
return { r, t: v - r };
|
|
3067
3138
|
}
|
|
3139
|
+
function gridCol(xs, x) {
|
|
3140
|
+
let c = 0;
|
|
3141
|
+
while (c < xs.length - 2 && xs[c + 1] < x) c++;
|
|
3142
|
+
return { c, t: clamp((x - xs[c]) / (xs[c + 1] - xs[c]), 0, 1) };
|
|
3143
|
+
}
|
|
3144
|
+
function rowWindow(ys, r, limit) {
|
|
3145
|
+
const n = ys.length;
|
|
3146
|
+
return [
|
|
3147
|
+
Math.max(0, Math.floor(ys[0] - ys[Math.max(0, r - 1)])),
|
|
3148
|
+
Math.min(limit, Math.ceil(ys[0] - ys[Math.min(n - 1, r + 1)]))
|
|
3149
|
+
];
|
|
3150
|
+
}
|
|
3068
3151
|
function renderedCrownBlend(m, ys) {
|
|
3069
3152
|
const wr = ys.map((y) => crownBlend(m, y));
|
|
3070
3153
|
return {
|
|
@@ -3140,10 +3223,11 @@ function paints(runs, px) {
|
|
|
3140
3223
|
}
|
|
3141
3224
|
return false;
|
|
3142
3225
|
}
|
|
3143
|
-
function outerRide(m, inner, coreAt, followAt) {
|
|
3226
|
+
function outerRide(m, inner, coreAt, followAt, capLimit) {
|
|
3144
3227
|
const f = m.frame;
|
|
3145
|
-
const
|
|
3146
|
-
if (
|
|
3228
|
+
const grid = m.hairFrontGrid;
|
|
3229
|
+
if (grid === void 0) return void 0;
|
|
3230
|
+
const { xs, ys } = grid;
|
|
3147
3231
|
const top = ys[0];
|
|
3148
3232
|
if (f.hairRuns("hair_front", top) === void 0 || f.hairRuns("hair_back", top) === void 0) {
|
|
3149
3233
|
return void 0;
|
|
@@ -3152,6 +3236,9 @@ function outerRide(m, inner, coreAt, followAt) {
|
|
|
3152
3236
|
const sides = [-1, 1];
|
|
3153
3237
|
const n = ys.length;
|
|
3154
3238
|
const count = Math.round(top - ys[n - 1]);
|
|
3239
|
+
let last = 0;
|
|
3240
|
+
while (last + 1 < n && ys[last + 1] >= capLimit) last++;
|
|
3241
|
+
const capRows = last === 0 ? 0 : Math.min(count, Math.floor(top - ys[last] - 0.5) + 1);
|
|
3155
3242
|
const below = (NOD.hairFront.down - NOD.hairBack.down) * f.hh;
|
|
3156
3243
|
const above = (NOD.hairBack.up - NOD.hairFront.up) * f.hh;
|
|
3157
3244
|
const j0 = Math.floor(-above);
|
|
@@ -3160,8 +3247,9 @@ function outerRide(m, inner, coreAt, followAt) {
|
|
|
3160
3247
|
(_, j) => f.hairRuns("hair_back", top - (j + j0) - 0.5)
|
|
3161
3248
|
);
|
|
3162
3249
|
const offset = sides.map(() => new Float64Array(count).fill(NaN));
|
|
3163
|
-
const
|
|
3164
|
-
const
|
|
3250
|
+
const reachOut = sides.map(() => new Float64Array(count));
|
|
3251
|
+
const reachIn = sides.map(() => new Float64Array(count));
|
|
3252
|
+
const bound = sides.map(() => new Uint8Array(count));
|
|
3165
3253
|
for (let i = 0; i < count; i++) {
|
|
3166
3254
|
const y = top - i - 0.5;
|
|
3167
3255
|
const front = f.hairRuns("hair_front", y);
|
|
@@ -3176,24 +3264,25 @@ function outerRide(m, inner, coreAt, followAt) {
|
|
|
3176
3264
|
inward = Math.min(inward, c.inward);
|
|
3177
3265
|
}
|
|
3178
3266
|
offset[k][i] = side * (edge - f.axisX);
|
|
3179
|
-
|
|
3180
|
-
|
|
3267
|
+
reachOut[k][i] = outward;
|
|
3268
|
+
reachIn[k][i] = inward;
|
|
3269
|
+
if (i < capRows && runReach(backRows[i - j0], edge, side).inward > 0)
|
|
3270
|
+
bound[k][i] = 1;
|
|
3181
3271
|
});
|
|
3182
3272
|
}
|
|
3183
3273
|
const end = new Float64Array(n * 2);
|
|
3184
3274
|
const lift = new Float64Array(n * 4);
|
|
3185
3275
|
for (let r = 0; r < n; r++) {
|
|
3186
|
-
const i0 =
|
|
3187
|
-
const i1 = Math.min(count, Math.ceil(top - ys[Math.min(n - 1, r + 1)]));
|
|
3276
|
+
const [i0, i1] = rowWindow(ys, r, count);
|
|
3188
3277
|
sides.forEach((side, k) => {
|
|
3189
3278
|
let d = Infinity;
|
|
3190
|
-
let
|
|
3191
|
-
let
|
|
3279
|
+
let outward = Infinity;
|
|
3280
|
+
let inward = Infinity;
|
|
3192
3281
|
for (let i = i0; i < i1; i++) {
|
|
3193
3282
|
if (Number.isNaN(offset[k][i])) continue;
|
|
3194
3283
|
d = Math.min(d, offset[k][i]);
|
|
3195
|
-
|
|
3196
|
-
|
|
3284
|
+
outward = Math.min(outward, reachOut[k][i]);
|
|
3285
|
+
inward = Math.min(inward, reachIn[k][i]);
|
|
3197
3286
|
}
|
|
3198
3287
|
end[r * 2 + k] = d;
|
|
3199
3288
|
if (d === Infinity || d <= inner) return;
|
|
@@ -3201,7 +3290,7 @@ function outerRide(m, inner, coreAt, followAt) {
|
|
|
3201
3290
|
sides.forEach((s, j) => {
|
|
3202
3291
|
const allow = Math.min(
|
|
3203
3292
|
coreAt(x, ys[r], s),
|
|
3204
|
-
s === side ?
|
|
3293
|
+
m.shellScale * (s === side ? outward : inward) - drift
|
|
3205
3294
|
);
|
|
3206
3295
|
lift[(r * 2 + k) * 2 + j] = Math.max(0, allow - followAt(x, ys[r], s));
|
|
3207
3296
|
});
|
|
@@ -3211,7 +3300,7 @@ function outerRide(m, inner, coreAt, followAt) {
|
|
|
3211
3300
|
const v = lift[(r * 2 + k) * 2 + j];
|
|
3212
3301
|
return v === 0 ? 0 : v * Math.min(1, (d - inner) / (end[r * 2 + k] - inner));
|
|
3213
3302
|
};
|
|
3214
|
-
|
|
3303
|
+
const liftAt = (x, y, s) => {
|
|
3215
3304
|
const d = Math.abs(x - f.axisX);
|
|
3216
3305
|
if (s === 0 || d <= inner) return 0;
|
|
3217
3306
|
const k = x < f.axisX ? 0 : 1;
|
|
@@ -3219,6 +3308,130 @@ function outerRide(m, inner, coreAt, followAt) {
|
|
|
3219
3308
|
const { r, t } = gridRow(ys, y, true);
|
|
3220
3309
|
return rowRide(r, k, j, d) * (1 - t) + rowRide(r + 1, k, j, d) * t;
|
|
3221
3310
|
};
|
|
3311
|
+
const nc = xs.length;
|
|
3312
|
+
const along = (v, x) => {
|
|
3313
|
+
const { c, t } = gridCol(xs, x);
|
|
3314
|
+
return v[c] + (v[c + 1] - v[c]) * t;
|
|
3315
|
+
};
|
|
3316
|
+
const readAt = (r, x, y) => {
|
|
3317
|
+
const { c, t: fx } = gridCol(xs, x);
|
|
3318
|
+
const dx = xs[c + 1] - xs[c];
|
|
3319
|
+
if (r + 1 < n && y <= ys[r] && y >= ys[r + 1]) {
|
|
3320
|
+
const fy = (ys[r] - y) / (ys[r] - ys[r + 1]);
|
|
3321
|
+
if (fy >= 1) return void 0;
|
|
3322
|
+
return fx <= fy ? xs[c] : xs[c] + (fx - fy) / (1 - fy) * dx;
|
|
3323
|
+
}
|
|
3324
|
+
if (r > 0 && y > ys[r] && y < ys[r - 1]) {
|
|
3325
|
+
const fy = (ys[r - 1] - y) / (ys[r - 1] - ys[r]);
|
|
3326
|
+
return fx <= fy ? xs[c] + fx / fy * dx : xs[c + 1];
|
|
3327
|
+
}
|
|
3328
|
+
return void 0;
|
|
3329
|
+
};
|
|
3330
|
+
const ahead = (e, s) => s === e.side ? e.outward : e.inward;
|
|
3331
|
+
const behind = (e, s) => s === e.side ? e.inward : e.outward;
|
|
3332
|
+
const capCut = (r, s, side, today, mine) => {
|
|
3333
|
+
const reach = Math.max(...mine.map((e) => side * (e.x - f.axisX)));
|
|
3334
|
+
const quarter = new Float64Array(nc);
|
|
3335
|
+
const cols = xs.map((_, c) => c).filter((c) => (xs[c] < f.axisX ? -1 : 1) === side);
|
|
3336
|
+
if (side < 0) cols.reverse();
|
|
3337
|
+
let total = 0;
|
|
3338
|
+
let x0 = f.axisX;
|
|
3339
|
+
let d0 = s * followAt(f.axisX, ys[r], s);
|
|
3340
|
+
let open = true;
|
|
3341
|
+
for (const c of cols) {
|
|
3342
|
+
if (open) {
|
|
3343
|
+
const d = (m.shellScale - 1) * (xs[c] - f.axisX) + s * today[c];
|
|
3344
|
+
const rest = side * (xs[c] - x0);
|
|
3345
|
+
const full = rest + side * (d - d0);
|
|
3346
|
+
total += Math.max(0, Math.min(full, rest)) / 4;
|
|
3347
|
+
x0 = xs[c];
|
|
3348
|
+
d0 = d;
|
|
3349
|
+
open = side * (xs[c] - f.axisX) < reach;
|
|
3350
|
+
}
|
|
3351
|
+
quarter[c] = total;
|
|
3352
|
+
}
|
|
3353
|
+
let scale = 0;
|
|
3354
|
+
for (const e of mine) {
|
|
3355
|
+
const need = along(today, e.at) - (m.shellScale * ahead(e, s) - drift);
|
|
3356
|
+
if (need <= 0) continue;
|
|
3357
|
+
const q = along(quarter, e.at);
|
|
3358
|
+
scale = Math.max(scale, q > 0 ? need / q : 1);
|
|
3359
|
+
}
|
|
3360
|
+
scale = Math.min(1, scale);
|
|
3361
|
+
return quarter.map((q) => scale * q);
|
|
3362
|
+
};
|
|
3363
|
+
const cuts = new Float64Array(n * 2 * nc);
|
|
3364
|
+
const blend = new Float64Array(n * 2);
|
|
3365
|
+
const wr = ys.map((y) => crownBlend(m, y));
|
|
3366
|
+
for (let r = 0; r <= last && capRows > 0; r++) {
|
|
3367
|
+
const w = wr[r];
|
|
3368
|
+
if (w <= 0) continue;
|
|
3369
|
+
const edges = [];
|
|
3370
|
+
const [i0, i1] = rowWindow(ys, r, capRows);
|
|
3371
|
+
for (let i = i0; i < i1; i++) {
|
|
3372
|
+
sides.forEach((side, k) => {
|
|
3373
|
+
if (bound[k][i] === 0) return;
|
|
3374
|
+
const x = f.axisX + side * offset[k][i];
|
|
3375
|
+
const at = readAt(r, x, top - i - 0.5);
|
|
3376
|
+
if (at === void 0) return;
|
|
3377
|
+
edges.push({
|
|
3378
|
+
side,
|
|
3379
|
+
x,
|
|
3380
|
+
at,
|
|
3381
|
+
outward: reachOut[k][i],
|
|
3382
|
+
inward: reachIn[k][i]
|
|
3383
|
+
});
|
|
3384
|
+
});
|
|
3385
|
+
}
|
|
3386
|
+
if (edges.length === 0) continue;
|
|
3387
|
+
sides.forEach((s, j) => {
|
|
3388
|
+
const today = xs.map((x) => followAt(x, ys[r], s) + liftAt(x, ys[r], s));
|
|
3389
|
+
const after = today.slice();
|
|
3390
|
+
if (w >= 1) {
|
|
3391
|
+
for (const side of sides) {
|
|
3392
|
+
const mine = edges.filter((e) => e.side === side);
|
|
3393
|
+
if (mine.length === 0) continue;
|
|
3394
|
+
const cut = capCut(r, s, side, today, mine);
|
|
3395
|
+
for (let c = 0; c < nc; c++) after[c] -= cut[c];
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
let b = 0;
|
|
3399
|
+
for (const e of edges) {
|
|
3400
|
+
const rel = w * (along(after, e.at) + drift);
|
|
3401
|
+
const hi = m.shellScale * ahead(e, s);
|
|
3402
|
+
const lo = -m.shellScale * behind(e, s);
|
|
3403
|
+
if (rel > hi) b = Math.max(b, 1 - hi / rel);
|
|
3404
|
+
else if (rel < lo) b = Math.max(b, 1 - lo / rel);
|
|
3405
|
+
}
|
|
3406
|
+
blend[r * 2 + j] = b;
|
|
3407
|
+
for (let c = 0; c < nc; c++)
|
|
3408
|
+
cuts[(r * 2 + j) * nc + c] = after[c] - today[c];
|
|
3409
|
+
});
|
|
3410
|
+
}
|
|
3411
|
+
for (let j = 0; j < 2; j++) {
|
|
3412
|
+
for (let r = n - 2; r >= 0; r--) {
|
|
3413
|
+
if (wr[r] < 1)
|
|
3414
|
+
blend[r * 2 + j] = Math.max(blend[r * 2 + j], blend[(r + 1) * 2 + j]);
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
const cutRow = (r, j, x) => r > last ? 0 : along(cuts.subarray((r * 2 + j) * nc, (r * 2 + j + 1) * nc), x);
|
|
3418
|
+
return {
|
|
3419
|
+
lift: liftAt,
|
|
3420
|
+
// Between vertex rows, as the render interpolates; a vertex reads its own
|
|
3421
|
+
// row's alone.
|
|
3422
|
+
cut: (x, y, s) => {
|
|
3423
|
+
if (s === 0) return 0;
|
|
3424
|
+
const j = s < 0 ? 0 : 1;
|
|
3425
|
+
const { r, t } = gridRow(ys, y, true);
|
|
3426
|
+
return cutRow(r, j, x) * (1 - t) + cutRow(r + 1, j, x) * t;
|
|
3427
|
+
},
|
|
3428
|
+
blend: (y, s) => {
|
|
3429
|
+
if (s === 0) return 0;
|
|
3430
|
+
const j = s < 0 ? 0 : 1;
|
|
3431
|
+
const { r, t } = gridRow(ys, y, true);
|
|
3432
|
+
return blend[r * 2 + j] * (1 - t) + blend[(r + 1) * 2 + j] * t;
|
|
3433
|
+
}
|
|
3434
|
+
};
|
|
3222
3435
|
}
|
|
3223
3436
|
function crownRide(m, cap, relAt) {
|
|
3224
3437
|
const f = m.frame;
|
|
@@ -3258,8 +3471,7 @@ function crownRide(m, cap, relAt) {
|
|
|
3258
3471
|
return rel[at];
|
|
3259
3472
|
};
|
|
3260
3473
|
const cellR = (x, y, k) => {
|
|
3261
|
-
|
|
3262
|
-
while (c < xs.length - 2 && xs[c + 1] < x) c++;
|
|
3474
|
+
const { c } = gridCol(xs, x);
|
|
3263
3475
|
const { r } = gridRow(ys, y);
|
|
3264
3476
|
return Math.max(
|
|
3265
3477
|
relOf(r, c, k),
|
|
@@ -3349,8 +3561,7 @@ function crownRide(m, cap, relAt) {
|
|
|
3349
3561
|
const out = wr.slice();
|
|
3350
3562
|
for (let r = 0; r < n; r++) {
|
|
3351
3563
|
if (wr[r] >= 1) continue;
|
|
3352
|
-
const i0 =
|
|
3353
|
-
const i1 = Math.min(count, Math.ceil(top - ys[Math.min(n - 1, r + 1)]));
|
|
3564
|
+
const [i0, i1] = rowWindow(ys, r, count);
|
|
3354
3565
|
let least = Infinity;
|
|
3355
3566
|
for (let i = i0; i < i1; i++) least = Math.min(least, slackAt(i, k));
|
|
3356
3567
|
out[r] = wr[r] + clamp(least, 0, 1 - wr[r]);
|
|
@@ -4019,6 +4230,18 @@ function fitTurn(ctx, targets, style, build) {
|
|
|
4019
4230
|
}
|
|
4020
4231
|
p.shell = bisect((k) => at(k) - s, SHELL_RANGE[0], SHELL_RANGE[1]);
|
|
4021
4232
|
}
|
|
4233
|
+
const covered = (shell) => farEarCovered(build({ ...p, shell }));
|
|
4234
|
+
if (!covered(p.shell) && covered(Math.max(1, p.shell))) {
|
|
4235
|
+
let lo = p.shell;
|
|
4236
|
+
let hi = Math.max(1, p.shell);
|
|
4237
|
+
for (let i = 0; i < 50; i++) {
|
|
4238
|
+
const mid = (lo + hi) / 2;
|
|
4239
|
+
if (covered(mid)) hi = mid;
|
|
4240
|
+
else lo = mid;
|
|
4241
|
+
}
|
|
4242
|
+
p.shell = hi;
|
|
4243
|
+
clamped.push("silhouetteRatio");
|
|
4244
|
+
}
|
|
4022
4245
|
const hb = ctx.restHalf;
|
|
4023
4246
|
const feature = (key, family, at, box) => {
|
|
4024
4247
|
const base = build(p);
|
|
@@ -4177,6 +4400,7 @@ function buildTurn(frame, landmarks, q) {
|
|
|
4177
4400
|
hairOuter: q.outline * face,
|
|
4178
4401
|
hairBack: A * hh * TURN.hairBack,
|
|
4179
4402
|
shellScale: q.shell,
|
|
4403
|
+
earFarScale: 1 + A * (TURN.earFarScale - 1),
|
|
4180
4404
|
...landmarks
|
|
4181
4405
|
};
|
|
4182
4406
|
}
|