@ikijs/editor 0.16.0 → 0.17.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 +23 -17
- package/dist/index.d.mts +41 -20
- package/dist/index.d.ts +41 -20
- package/dist/index.js +866 -357
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +866 -355
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -648,6 +648,12 @@ var CaptureGridKeyform = class {
|
|
|
648
648
|
prevKeyforms;
|
|
649
649
|
apply(doc) {
|
|
650
650
|
const deformer = doc.findWarpDeformer(this.deformerId);
|
|
651
|
+
const count = deformer.warps?.length ?? 0;
|
|
652
|
+
if (count > 1) {
|
|
653
|
+
throw new Error(
|
|
654
|
+
`deformers."${this.deformerId}".warps: ${count} grid warps \u2014 capture targets a deformer with exactly one grid warp (which entry to write into is ambiguous)`
|
|
655
|
+
);
|
|
656
|
+
}
|
|
651
657
|
const warp = deformer.warps?.[0];
|
|
652
658
|
if (!warp) {
|
|
653
659
|
throw new Error(
|
|
@@ -1553,6 +1559,14 @@ function facePlateHalfOf(layers) {
|
|
|
1553
1559
|
return face.cropW / 2;
|
|
1554
1560
|
}
|
|
1555
1561
|
var STRAND_MIN_RUN_FRACTION = 0.5;
|
|
1562
|
+
var ROW_RUN_ROLES = /* @__PURE__ */ new Set([
|
|
1563
|
+
"face",
|
|
1564
|
+
"hair_front",
|
|
1565
|
+
"hair_back",
|
|
1566
|
+
"body",
|
|
1567
|
+
"arm_L",
|
|
1568
|
+
"arm_R"
|
|
1569
|
+
]);
|
|
1556
1570
|
function createLayerSetMeasurer(canvas) {
|
|
1557
1571
|
for (const field of ["width", "height"]) {
|
|
1558
1572
|
const value = canvas[field];
|
|
@@ -1582,7 +1596,7 @@ function createLayerSetMeasurer(canvas) {
|
|
|
1582
1596
|
if (bbox === null) return null;
|
|
1583
1597
|
const rowLeft = new Int32Array(canvasH).fill(canvasW);
|
|
1584
1598
|
const rowRight = new Int32Array(canvasH).fill(-1);
|
|
1585
|
-
const rowRuns = role
|
|
1599
|
+
const rowRuns = ROW_RUN_ROLES.has(role) ? [] : void 0;
|
|
1586
1600
|
for (let y = 0; y < canvasH; y++) {
|
|
1587
1601
|
let runs;
|
|
1588
1602
|
if (rowRuns !== void 0 && y >= bbox.y && y < bbox.y + bbox.h) {
|
|
@@ -1924,7 +1938,13 @@ var PROFILE_VALUES = {
|
|
|
1924
1938
|
brow: 0.15,
|
|
1925
1939
|
breath: 0.03,
|
|
1926
1940
|
sway: 0.08,
|
|
1927
|
-
blushRest: 0.4
|
|
1941
|
+
blushRest: 0.4,
|
|
1942
|
+
bodySlide: 0.1,
|
|
1943
|
+
bodyNarrow: 0.05,
|
|
1944
|
+
bodyBow: 0.06,
|
|
1945
|
+
bodyRoll: 4,
|
|
1946
|
+
bodyFollowX: 0.3,
|
|
1947
|
+
bodyFollowZ: 0.3
|
|
1928
1948
|
};
|
|
1929
1949
|
function deriveProfile(v) {
|
|
1930
1950
|
const farLead = v.lead * (1 - v.fore / 2);
|
|
@@ -2018,9 +2038,32 @@ function deriveProfile(v) {
|
|
|
2018
2038
|
* slide, to the next 0.05. */
|
|
2019
2039
|
hiddenNeck: Math.ceil(30 * (v.slide + v.lead) - 1e-9) / 20
|
|
2020
2040
|
};
|
|
2021
|
-
|
|
2041
|
+
const body = {
|
|
2042
|
+
/** BodyAngleX: the upper body's slide, hh. */
|
|
2043
|
+
slide: v.bodySlide,
|
|
2044
|
+
/** BodyAngleX: how much narrower the upper body gets, either way. */
|
|
2045
|
+
narrow: v.bodyNarrow,
|
|
2046
|
+
/** BodyAngleY: the upper body's rise (+) or bow (−), hh. */
|
|
2047
|
+
bow: v.bodyBow,
|
|
2048
|
+
/** BodyAngleZ: the upper body's roll about the hips, degrees. */
|
|
2049
|
+
rollDeg: v.bodyRoll,
|
|
2050
|
+
/** AngleX: the body's follow, as a share of BodyAngleX's motion. */
|
|
2051
|
+
followX: v.bodyFollowX,
|
|
2052
|
+
/** AngleZ: the body's follow, as a share of its roll. */
|
|
2053
|
+
followZ: v.bodyFollowZ,
|
|
2054
|
+
/** The roll the follow gives the body at AngleZ ±30, degrees: β, the
|
|
2055
|
+
* share of `ROLL_DEG` the head's own roll leaves to the body. */
|
|
2056
|
+
followRoll: v.bodyFollowZ * v.bodyRoll
|
|
2057
|
+
};
|
|
2058
|
+
return {
|
|
2059
|
+
TURN: turn,
|
|
2060
|
+
NOD: nod,
|
|
2061
|
+
ROLL_DEG: v.rollDeg,
|
|
2062
|
+
AMPLITUDE: amplitude,
|
|
2063
|
+
BODY: body
|
|
2064
|
+
};
|
|
2022
2065
|
}
|
|
2023
|
-
var { TURN, NOD, ROLL_DEG, AMPLITUDE } = deriveProfile(PROFILE_VALUES);
|
|
2066
|
+
var { TURN, NOD, ROLL_DEG, AMPLITUDE, BODY } = deriveProfile(PROFILE_VALUES);
|
|
2024
2067
|
var STYLE_KNOBS = [
|
|
2025
2068
|
"turn",
|
|
2026
2069
|
"featureLead",
|
|
@@ -2050,6 +2093,12 @@ var TurnTargetError = class extends Error {
|
|
|
2050
2093
|
this.name = "TurnTargetError";
|
|
2051
2094
|
}
|
|
2052
2095
|
};
|
|
2096
|
+
var LayerGeometryError = class extends Error {
|
|
2097
|
+
constructor(message) {
|
|
2098
|
+
super(message);
|
|
2099
|
+
this.name = "LayerGeometryError";
|
|
2100
|
+
}
|
|
2101
|
+
};
|
|
2053
2102
|
var DEFAULT_TURN_TARGETS = Object.freeze({
|
|
2054
2103
|
eyeShift: round2((TURN.eyeFar + TURN.eyeNear) / 2 - TURN.hairBack),
|
|
2055
2104
|
farEyeRatio: round2(TURN.eyeFarScale / TURN.eyeNearScale),
|
|
@@ -2060,6 +2109,8 @@ var DEFAULT_TURN_TARGETS = Object.freeze({
|
|
|
2060
2109
|
var ROLE_TABLE = [
|
|
2061
2110
|
{ role: "hair_back", family: "hair_back" },
|
|
2062
2111
|
{ role: "body", family: "body" },
|
|
2112
|
+
{ role: "arm_L", family: "arm", parts: ["arm_L", "forearm_L"] },
|
|
2113
|
+
{ role: "arm_R", family: "arm", parts: ["arm_R", "forearm_R"] },
|
|
2063
2114
|
{ role: "face", family: "face", required: true },
|
|
2064
2115
|
{ role: "blush_L", family: "face" },
|
|
2065
2116
|
{ role: "blush_R", family: "face" },
|
|
@@ -2091,6 +2142,15 @@ function roleSpec(role) {
|
|
|
2091
2142
|
if (spec === void 0) throw new Error(`auto-rig: unknown role "${role}"`);
|
|
2092
2143
|
return spec;
|
|
2093
2144
|
}
|
|
2145
|
+
function partIdsOfRole(role) {
|
|
2146
|
+
return roleSpec(role).parts ?? [role];
|
|
2147
|
+
}
|
|
2148
|
+
var ROLE_BY_PART = new Map(
|
|
2149
|
+
ROLE_TABLE.flatMap((r) => (r.parts ?? []).map((p) => [p, r.role]))
|
|
2150
|
+
);
|
|
2151
|
+
function roleOfPart(partId) {
|
|
2152
|
+
return ROLE_BY_PART.get(partId) ?? partId;
|
|
2153
|
+
}
|
|
2094
2154
|
var ALIASES = {
|
|
2095
2155
|
eyebrow_L: "brow_L",
|
|
2096
2156
|
eyebrow_R: "brow_R",
|
|
@@ -2101,6 +2161,16 @@ function roleOf(fileName) {
|
|
|
2101
2161
|
const role = fileName.replace(/\.[^.]+$/, "").toLowerCase().replace(/[-\s]+/g, "_").replace(/_([lr])$/, (_, s) => `_${s.toUpperCase()}`);
|
|
2102
2162
|
return ALIASES[role] ?? role;
|
|
2103
2163
|
}
|
|
2164
|
+
function checkArmsHaveBody(roles) {
|
|
2165
|
+
const has = new Set(roles);
|
|
2166
|
+
if (has.has("body")) return;
|
|
2167
|
+
const arm = ROLE_TABLE.find((r) => r.family === "arm" && has.has(r.role));
|
|
2168
|
+
if (arm !== void 0) {
|
|
2169
|
+
throw new Error(
|
|
2170
|
+
`auto-rig: ${arm.role} needs a body layer (an arm hangs from the body's shoulder)`
|
|
2171
|
+
);
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2104
2174
|
function parseLayerRoles(fileNames) {
|
|
2105
2175
|
const seen = /* @__PURE__ */ new Map();
|
|
2106
2176
|
const out = [];
|
|
@@ -2126,13 +2196,14 @@ function parseLayerRoles(fileNames) {
|
|
|
2126
2196
|
`auto-rig: missing required role${missing.length > 1 ? "s" : ""} ${missing.join(", ")}`
|
|
2127
2197
|
);
|
|
2128
2198
|
}
|
|
2199
|
+
checkArmsHaveBody(seen.keys());
|
|
2129
2200
|
return out;
|
|
2130
2201
|
}
|
|
2131
2202
|
|
|
2132
2203
|
// src/auto-rig/generate.ts
|
|
2133
2204
|
import {
|
|
2134
2205
|
IKI_FORMAT_VERSION as IKI_FORMAT_VERSION2,
|
|
2135
|
-
StandardParameter as
|
|
2206
|
+
StandardParameter as P6,
|
|
2136
2207
|
parseIkiModel as parseIkiModel3
|
|
2137
2208
|
} from "@ikijs/format";
|
|
2138
2209
|
|
|
@@ -2287,6 +2358,16 @@ var DEFAULT_MOTIONS = {
|
|
|
2287
2358
|
]
|
|
2288
2359
|
};
|
|
2289
2360
|
|
|
2361
|
+
// src/auto-rig/arms.ts
|
|
2362
|
+
import {
|
|
2363
|
+
StandardParameter as P4
|
|
2364
|
+
} from "@ikijs/format";
|
|
2365
|
+
|
|
2366
|
+
// src/auto-rig/body.ts
|
|
2367
|
+
import {
|
|
2368
|
+
StandardParameter as P3
|
|
2369
|
+
} from "@ikijs/format";
|
|
2370
|
+
|
|
2290
2371
|
// src/auto-rig/layout.ts
|
|
2291
2372
|
function boxOfLayer(layer) {
|
|
2292
2373
|
return boxOfPixels(layer.bbox, layer.canvasW, layer.canvasH);
|
|
@@ -2360,6 +2441,653 @@ function meshPoints(mesh, box) {
|
|
|
2360
2441
|
return out;
|
|
2361
2442
|
}
|
|
2362
2443
|
|
|
2444
|
+
// src/auto-rig/drivers.ts
|
|
2445
|
+
import {
|
|
2446
|
+
StandardParameter as P2
|
|
2447
|
+
} from "@ikijs/format";
|
|
2448
|
+
var SWAY_RANGE = 20;
|
|
2449
|
+
var SWAY_CURVE = 1.4;
|
|
2450
|
+
var HAIR_HANG = 0.35;
|
|
2451
|
+
var Z_STOPS = [-30, 0, 30];
|
|
2452
|
+
function localWarp(parameter, mesh, box, values, f) {
|
|
2453
|
+
const pts = meshPoints(mesh, box);
|
|
2454
|
+
const w = bw(box);
|
|
2455
|
+
const h = bh(box);
|
|
2456
|
+
return {
|
|
2457
|
+
parameter,
|
|
2458
|
+
keyforms: values.map((value) => ({
|
|
2459
|
+
value,
|
|
2460
|
+
offsets: pts.flatMap((p, i) => {
|
|
2461
|
+
const [dx, dy] = f(p, value, i);
|
|
2462
|
+
return [roundTo(dx / w, 1e-4), roundTo(dy / h, 1e-4)];
|
|
2463
|
+
})
|
|
2464
|
+
}))
|
|
2465
|
+
};
|
|
2466
|
+
}
|
|
2467
|
+
var rollOf = (v, deg = ROLL_DEG) => -v / 30 * deg * DEG;
|
|
2468
|
+
function motionReach(warps, mesh, box, extra = [0, 0]) {
|
|
2469
|
+
const out = {
|
|
2470
|
+
left: extra[0],
|
|
2471
|
+
right: extra[0],
|
|
2472
|
+
bottom: extra[1],
|
|
2473
|
+
top: extra[1]
|
|
2474
|
+
};
|
|
2475
|
+
if (mesh === void 0) return out;
|
|
2476
|
+
const pts = meshPoints(mesh, box);
|
|
2477
|
+
for (const warp of warps ?? []) {
|
|
2478
|
+
let l = 0;
|
|
2479
|
+
let r = 0;
|
|
2480
|
+
let b = 0;
|
|
2481
|
+
let t = 0;
|
|
2482
|
+
for (const k of warp.keyforms) {
|
|
2483
|
+
pts.forEach(([x, y], i) => {
|
|
2484
|
+
const px = x + k.offsets[2 * i] * bw(box);
|
|
2485
|
+
const py = y + k.offsets[2 * i + 1] * bh(box);
|
|
2486
|
+
l = Math.max(l, box.x0 - px);
|
|
2487
|
+
r = Math.max(r, px - box.x1);
|
|
2488
|
+
b = Math.max(b, box.y0 - py);
|
|
2489
|
+
t = Math.max(t, py - box.y1);
|
|
2490
|
+
});
|
|
2491
|
+
}
|
|
2492
|
+
out.left += l;
|
|
2493
|
+
out.right += r;
|
|
2494
|
+
out.bottom += b;
|
|
2495
|
+
out.top += t;
|
|
2496
|
+
}
|
|
2497
|
+
return out;
|
|
2498
|
+
}
|
|
2499
|
+
function blinkFold(param, mesh, box, travel, eye = box) {
|
|
2500
|
+
const yc = eye.y1 - travel * bh(eye);
|
|
2501
|
+
return localWarp(
|
|
2502
|
+
param,
|
|
2503
|
+
mesh,
|
|
2504
|
+
box,
|
|
2505
|
+
[0, 1],
|
|
2506
|
+
([, y], v) => v === 1 ? [0, 0] : [0, yc - y]
|
|
2507
|
+
);
|
|
2508
|
+
}
|
|
2509
|
+
function lashFold(param, mesh, lash, eye, travel) {
|
|
2510
|
+
const yc = eye.y1 - travel * bh(eye) - 0.04 * bh(eye);
|
|
2511
|
+
const c = cx(lash);
|
|
2512
|
+
const half = bw(lash) / 2;
|
|
2513
|
+
const sag = 0.3 * bh(lash);
|
|
2514
|
+
return localWarp(param, mesh, lash, [0, 1], ([x, y], v) => {
|
|
2515
|
+
if (v === 1) return [0, 0];
|
|
2516
|
+
const u = (x - c) / half;
|
|
2517
|
+
return [0, yc + (y - lash.y0) * 0.5 - sag * (1 - u * u) - y];
|
|
2518
|
+
});
|
|
2519
|
+
}
|
|
2520
|
+
function gazeBindings(irisW, share = 1) {
|
|
2521
|
+
const gx = AMPLITUDE.gazeX * irisW * share;
|
|
2522
|
+
const gy = AMPLITUDE.gazeY * irisW * share;
|
|
2523
|
+
return [
|
|
2524
|
+
{
|
|
2525
|
+
parameter: P2.EyeballX,
|
|
2526
|
+
channel: "translateX",
|
|
2527
|
+
from: -round1(gx),
|
|
2528
|
+
to: round1(gx)
|
|
2529
|
+
},
|
|
2530
|
+
{
|
|
2531
|
+
parameter: P2.EyeballY,
|
|
2532
|
+
channel: "translateY",
|
|
2533
|
+
from: -round1(gy),
|
|
2534
|
+
to: round1(gy)
|
|
2535
|
+
}
|
|
2536
|
+
];
|
|
2537
|
+
}
|
|
2538
|
+
function gazeReach(irisW, share = 1) {
|
|
2539
|
+
return [AMPLITUDE.gazeX * irisW * share, AMPLITUDE.gazeY * irisW * share];
|
|
2540
|
+
}
|
|
2541
|
+
function browReach(hh) {
|
|
2542
|
+
return AMPLITUDE.brow * hh;
|
|
2543
|
+
}
|
|
2544
|
+
function browBindings(side, reach) {
|
|
2545
|
+
const y = side === "L" ? P2.BrowLeftY : P2.BrowRightY;
|
|
2546
|
+
const a = side === "L" ? P2.BrowLeftAngle : P2.BrowRightAngle;
|
|
2547
|
+
return [
|
|
2548
|
+
{
|
|
2549
|
+
parameter: y,
|
|
2550
|
+
channel: "translateY",
|
|
2551
|
+
from: -round1(reach),
|
|
2552
|
+
to: round1(reach)
|
|
2553
|
+
},
|
|
2554
|
+
{ parameter: a, channel: "rotate", from: -BROW_TILT, to: BROW_TILT }
|
|
2555
|
+
];
|
|
2556
|
+
}
|
|
2557
|
+
var BROW_TILT = 15;
|
|
2558
|
+
function mouthForm(mesh, box) {
|
|
2559
|
+
const a = 0.3 * bh(box);
|
|
2560
|
+
const c = cx(box);
|
|
2561
|
+
const half = bw(box) / 2;
|
|
2562
|
+
return localWarp(P2.MouthForm, mesh, box, [-1, 0, 1], ([x], v) => {
|
|
2563
|
+
if (v === 0) return [0, 0];
|
|
2564
|
+
const u = (x - c) / half;
|
|
2565
|
+
const widen = v > 0 ? 0.05 : 0.03;
|
|
2566
|
+
return [v * widen * (x - c), v * a * (u * u - 0.3)];
|
|
2567
|
+
});
|
|
2568
|
+
}
|
|
2569
|
+
function mouthOpenGrow(mesh, box) {
|
|
2570
|
+
const c = cx(box);
|
|
2571
|
+
return localWarp(
|
|
2572
|
+
P2.MouthOpen,
|
|
2573
|
+
mesh,
|
|
2574
|
+
box,
|
|
2575
|
+
[0, 1],
|
|
2576
|
+
([x, y], v) => v === 1 ? [(AMPLITUDE.mouthOpenWidth - 1) * (x - c), 0] : [0, box.y1 + (y - box.y1) * 0.2 - y]
|
|
2577
|
+
);
|
|
2578
|
+
}
|
|
2579
|
+
function mouthWiden(mesh, box) {
|
|
2580
|
+
const c = cx(box);
|
|
2581
|
+
return localWarp(P2.MouthOpen, mesh, box, [0, 1], ([x], v) => [
|
|
2582
|
+
v * (AMPLITUDE.mouthOpenWidth - 1) * (x - c),
|
|
2583
|
+
0
|
|
2584
|
+
]);
|
|
2585
|
+
}
|
|
2586
|
+
function hairSway(param, mesh, box, amp) {
|
|
2587
|
+
return localWarp(
|
|
2588
|
+
param,
|
|
2589
|
+
mesh,
|
|
2590
|
+
box,
|
|
2591
|
+
[-SWAY_RANGE, 0, SWAY_RANGE],
|
|
2592
|
+
([, y], v) => {
|
|
2593
|
+
const t = Math.max(0, (box.y1 - y) / bh(box));
|
|
2594
|
+
return [v / SWAY_RANGE * amp * t ** SWAY_CURVE, 0];
|
|
2595
|
+
}
|
|
2596
|
+
);
|
|
2597
|
+
}
|
|
2598
|
+
function tiltHang(mesh, box, pivot, weight, deg = ROLL_DEG) {
|
|
2599
|
+
return localWarp(P2.AngleZ, mesh, box, Z_STOPS, ([x, y], v, i) => {
|
|
2600
|
+
const g = weight(y, i);
|
|
2601
|
+
if (g === 0 || v === 0) return [0, 0];
|
|
2602
|
+
const a = -g * rollOf(v, deg);
|
|
2603
|
+
const px = x - pivot[0];
|
|
2604
|
+
const py = y - pivot[1];
|
|
2605
|
+
return [
|
|
2606
|
+
pivot[0] + px * Math.cos(a) - py * Math.sin(a) - x,
|
|
2607
|
+
pivot[1] + px * Math.sin(a) + py * Math.cos(a) - y
|
|
2608
|
+
];
|
|
2609
|
+
});
|
|
2610
|
+
}
|
|
2611
|
+
function hairHangWeight(pivotY, bottom) {
|
|
2612
|
+
if (bottom >= pivotY) return () => 0;
|
|
2613
|
+
return (y) => HAIR_HANG * smoothstep(pivotY, bottom, y);
|
|
2614
|
+
}
|
|
2615
|
+
var round1 = (v) => roundTo(v, 0.1);
|
|
2616
|
+
|
|
2617
|
+
// src/auto-rig/grid.ts
|
|
2618
|
+
var X_STOPS = [-30, 0, 30];
|
|
2619
|
+
var Y_STOPS = [-30, 0, 30];
|
|
2620
|
+
function lattice(box, cell, min, max) {
|
|
2621
|
+
const cols = cellsFor(box.x1 - box.x0, cell, min, max);
|
|
2622
|
+
const rows = cellsFor(box.y1 - box.y0, cell, min, max);
|
|
2623
|
+
const points = [];
|
|
2624
|
+
for (let r = 0; r <= rows; r++) {
|
|
2625
|
+
const y = box.y1 - (box.y1 - box.y0) * r / rows;
|
|
2626
|
+
for (let c = 0; c <= cols; c++) {
|
|
2627
|
+
points.push(
|
|
2628
|
+
roundTo(box.x0 + (box.x1 - box.x0) * c / cols, 0.01),
|
|
2629
|
+
roundTo(y, 0.01)
|
|
2630
|
+
);
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
return { box, cols, rows, points };
|
|
2634
|
+
}
|
|
2635
|
+
function bake(l, field) {
|
|
2636
|
+
const out = [];
|
|
2637
|
+
for (const ay of Y_STOPS) {
|
|
2638
|
+
for (const ax of X_STOPS) {
|
|
2639
|
+
const offsets = [];
|
|
2640
|
+
for (let k = 0; k < l.points.length; k += 2) {
|
|
2641
|
+
if (ax === 0 && ay === 0) {
|
|
2642
|
+
offsets.push(0, 0);
|
|
2643
|
+
continue;
|
|
2644
|
+
}
|
|
2645
|
+
const [dx, dy] = field(l.points[k], l.points[k + 1], ax, ay);
|
|
2646
|
+
offsets.push(roundTo(dx, 0.1), roundTo(dy, 0.1));
|
|
2647
|
+
}
|
|
2648
|
+
out.push(offsets);
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
return out;
|
|
2652
|
+
}
|
|
2653
|
+
function keyformAt(keyforms, ax) {
|
|
2654
|
+
const j = Y_STOPS.indexOf(0);
|
|
2655
|
+
const row = X_STOPS.map((_, i) => keyforms[j * X_STOPS.length + i]);
|
|
2656
|
+
return blendStops(X_STOPS, row, ax);
|
|
2657
|
+
}
|
|
2658
|
+
function blendStops(values, offsets, v) {
|
|
2659
|
+
const x = clamp(v, values[0], values[values.length - 1]);
|
|
2660
|
+
let i = 0;
|
|
2661
|
+
while (i < values.length - 2 && x > values[i + 1]) i++;
|
|
2662
|
+
const t = (x - values[i]) / (values[i + 1] - values[i]);
|
|
2663
|
+
return offsets[i].map((o, k) => o + (offsets[i + 1][k] - o) * t);
|
|
2664
|
+
}
|
|
2665
|
+
function land(l, offsets, x, y) {
|
|
2666
|
+
const { cols, rows, points } = l;
|
|
2667
|
+
const stride = cols + 1;
|
|
2668
|
+
let col = cols - 1;
|
|
2669
|
+
for (let c = 0; c < cols; c++) {
|
|
2670
|
+
if (x < points[(c + 1) * 2]) {
|
|
2671
|
+
col = c;
|
|
2672
|
+
break;
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
let row = rows - 1;
|
|
2676
|
+
for (let r = 0; r < rows; r++) {
|
|
2677
|
+
if (y > points[(r + 1) * stride * 2 + 1]) {
|
|
2678
|
+
row = r;
|
|
2679
|
+
break;
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
const x0 = points[col * 2];
|
|
2683
|
+
const x1 = points[(col + 1) * 2];
|
|
2684
|
+
const yT = points[row * stride * 2 + 1];
|
|
2685
|
+
const yB = points[(row + 1) * stride * 2 + 1];
|
|
2686
|
+
const s = clamp((x - x0) / (x1 - x0), 0, 1);
|
|
2687
|
+
const t = clamp((yT - y) / (yT - yB), 0, 1);
|
|
2688
|
+
const at = (r, c) => {
|
|
2689
|
+
const k = (r * stride + c) * 2;
|
|
2690
|
+
return [points[k] + offsets[k], points[k + 1] + offsets[k + 1]];
|
|
2691
|
+
};
|
|
2692
|
+
const p00 = at(row, col);
|
|
2693
|
+
const p10 = at(row, col + 1);
|
|
2694
|
+
const p01 = at(row + 1, col);
|
|
2695
|
+
const p11 = at(row + 1, col + 1);
|
|
2696
|
+
const topX = p00[0] + (p10[0] - p00[0]) * s;
|
|
2697
|
+
const topY = p00[1] + (p10[1] - p00[1]) * s;
|
|
2698
|
+
const botX = p01[0] + (p11[0] - p01[0]) * s;
|
|
2699
|
+
const botY = p01[1] + (p11[1] - p01[1]) * s;
|
|
2700
|
+
return [topX + (botX - topX) * t, topY + (botY - topY) * t];
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
// src/auto-rig/body.ts
|
|
2704
|
+
var BODY_WARP_ID = "bodyWarp";
|
|
2705
|
+
var BODY_GRID_PAD = 6;
|
|
2706
|
+
var CHEST_DROP = 0.8;
|
|
2707
|
+
var HIP_BAND = [0.35, 0.75];
|
|
2708
|
+
var LEG_MIN = 0.1;
|
|
2709
|
+
var HIP_FRACTION = 0.5;
|
|
2710
|
+
var BODY_MESH_PX = 48;
|
|
2711
|
+
var BODY_STOPS = [-10, 0, 10];
|
|
2712
|
+
var FOLD_MARGIN = 0.01;
|
|
2713
|
+
function hipLine(layer) {
|
|
2714
|
+
const { bbox, canvasH, rowRuns } = layer;
|
|
2715
|
+
const top = canvasH / 2 - bbox.y;
|
|
2716
|
+
if (rowRuns !== void 0) {
|
|
2717
|
+
const legMin = LEG_MIN * bbox.w;
|
|
2718
|
+
const last = Math.floor(HIP_BAND[1] * bbox.h);
|
|
2719
|
+
for (let k = Math.ceil(HIP_BAND[0] * bbox.h); k <= last; k++) {
|
|
2720
|
+
const runs = rowRuns[k];
|
|
2721
|
+
let legs = 0;
|
|
2722
|
+
for (let i = 0; i < runs.length; i += 2) {
|
|
2723
|
+
if (runs[i + 1] - runs[i] >= legMin) legs++;
|
|
2724
|
+
}
|
|
2725
|
+
if (legs >= 2) return roundTo(top - k, 0.01);
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
if (bbox.y + bbox.h === canvasH) return roundTo(top - bbox.h, 0.01);
|
|
2729
|
+
return roundTo(top - HIP_FRACTION * bbox.h, 0.01);
|
|
2730
|
+
}
|
|
2731
|
+
function bodyLattice(body, chin, hh, pivots, anchor) {
|
|
2732
|
+
const b = boxOfLayer(body);
|
|
2733
|
+
const g = (v) => Math.round(roundTo(v, 0.01) * 100);
|
|
2734
|
+
const pad = BODY_GRID_PAD * 100;
|
|
2735
|
+
const all = [chin, ...pivots];
|
|
2736
|
+
const a = g(anchor);
|
|
2737
|
+
const p = Math.min(...all.map((q) => g(q.y)));
|
|
2738
|
+
if (p - a < 2) {
|
|
2739
|
+
throw new LayerGeometryError(
|
|
2740
|
+
`auto-rig: layer "${body.fileName}": its hips (y ${anchor}) leave no row line strictly between them and the lowest of the head's and shoulders' pivots (y ${p / 100}), so the body warp cannot plant the legs below the pivots`
|
|
2741
|
+
);
|
|
2742
|
+
}
|
|
2743
|
+
let full = Math.min(g(chin.y - CHEST_DROP * hh), p - pad);
|
|
2744
|
+
if (full - a < pad) {
|
|
2745
|
+
full = Math.min(Math.max(Math.round((a + p) / 2), a + 1), p - 1);
|
|
2746
|
+
}
|
|
2747
|
+
const top = Math.max(g(b.y1), ...all.map((q) => g(q.y))) + pad;
|
|
2748
|
+
const bottom = Math.min(a, g(b.y0)) - pad;
|
|
2749
|
+
const split = (from, to, cells) => {
|
|
2750
|
+
const n = Math.max(1, Math.min(cells, from - to));
|
|
2751
|
+
const out = [from];
|
|
2752
|
+
for (let i = 1; i < n; i++) {
|
|
2753
|
+
out.push(Math.round(from - (from - to) * i / n));
|
|
2754
|
+
}
|
|
2755
|
+
out.push(to);
|
|
2756
|
+
return out;
|
|
2757
|
+
};
|
|
2758
|
+
const bandA = split(top, full, cellsFor((top - full) / 100, 96, 1, 8));
|
|
2759
|
+
const ramp = split(full, a, cellsFor((full - a) / 100, 96, 2, 8));
|
|
2760
|
+
const rows = [...bandA, ...ramp.slice(1, -1)];
|
|
2761
|
+
const ys = [...rows.map((y) => y / 100), anchor, bottom / 100];
|
|
2762
|
+
const weights = [...rows.map((y) => smoothstep(a, full, y)), 0, 0];
|
|
2763
|
+
const x0 = Math.min(b.x0, ...all.map((q) => q.x)) - BODY_GRID_PAD;
|
|
2764
|
+
const x1 = Math.max(b.x1, ...all.map((q) => q.x)) + BODY_GRID_PAD;
|
|
2765
|
+
const cols = cellsFor(x1 - x0, 96, 2, 8);
|
|
2766
|
+
const xs = [];
|
|
2767
|
+
for (let c = 0; c <= cols; c++) {
|
|
2768
|
+
xs.push(roundTo(x0 + (x1 - x0) * c / cols, 0.01));
|
|
2769
|
+
}
|
|
2770
|
+
return { xs, ys, weights, yFull: full / 100 };
|
|
2771
|
+
}
|
|
2772
|
+
function bodyMesh(b, rows) {
|
|
2773
|
+
const lines = [b.y1, ...rows.filter((y) => y > b.y0 && y < b.y1), b.y0];
|
|
2774
|
+
const ys = [lines[0]];
|
|
2775
|
+
for (let i = 1; i < lines.length; i++) {
|
|
2776
|
+
const [from, to] = [lines[i - 1], lines[i]];
|
|
2777
|
+
const n = Math.ceil((from - to) / BODY_MESH_PX);
|
|
2778
|
+
for (let k = 1; k < n; k++) ys.push(from - (from - to) * k / n);
|
|
2779
|
+
ys.push(to);
|
|
2780
|
+
}
|
|
2781
|
+
const cols = cellsFor(bw(b), BODY_MESH_PX, 2, 16);
|
|
2782
|
+
const vertices = [];
|
|
2783
|
+
const uvs = [];
|
|
2784
|
+
for (const y of ys) {
|
|
2785
|
+
const vy = roundTo((y - cy(b)) / bh(b), 1e-5);
|
|
2786
|
+
for (let c = 0; c <= cols; c++) {
|
|
2787
|
+
const vx = roundTo(c / cols - 0.5, 1e-5);
|
|
2788
|
+
vertices.push(vx, vy);
|
|
2789
|
+
uvs.push(roundTo(vx + 0.5, 1e-5), roundTo(0.5 - vy, 1e-5));
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
const indices = [];
|
|
2793
|
+
for (let r = 0; r + 1 < ys.length; r++) {
|
|
2794
|
+
for (let c = 0; c < cols; c++) {
|
|
2795
|
+
const tl = r * (cols + 1) + c;
|
|
2796
|
+
const tr = tl + 1;
|
|
2797
|
+
const bl = tl + cols + 1;
|
|
2798
|
+
const br = bl + 1;
|
|
2799
|
+
indices.push(bl, br, tl, tl, br, tr);
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
return { vertices, uvs, indices };
|
|
2803
|
+
}
|
|
2804
|
+
function buildBodyWarp({
|
|
2805
|
+
body,
|
|
2806
|
+
chin,
|
|
2807
|
+
hh,
|
|
2808
|
+
pivots
|
|
2809
|
+
}) {
|
|
2810
|
+
const b = boxOfLayer(body);
|
|
2811
|
+
const anchor = hipLine(body);
|
|
2812
|
+
const { xs, ys, weights } = bodyLattice(body, chin, hh, pivots, anchor);
|
|
2813
|
+
const hx = cx(b);
|
|
2814
|
+
const turn = (x, t) => t * BODY.slide * hh - Math.abs(t) * BODY.narrow * (x - hx);
|
|
2815
|
+
const roll = (x, y, deg) => {
|
|
2816
|
+
const c = Math.cos(deg * DEG) - 1;
|
|
2817
|
+
const s = Math.sin(deg * DEG);
|
|
2818
|
+
const [dx, dy] = [x - hx, y - anchor];
|
|
2819
|
+
return [c * dx - s * dy, s * dx + c * dy];
|
|
2820
|
+
};
|
|
2821
|
+
const warp = (parameter, values, field) => {
|
|
2822
|
+
const extreme = Math.max(...values.map(Math.abs));
|
|
2823
|
+
return {
|
|
2824
|
+
parameter,
|
|
2825
|
+
keyforms: values.map((value) => ({
|
|
2826
|
+
value,
|
|
2827
|
+
offsets: ys.flatMap(
|
|
2828
|
+
(y, r) => xs.flatMap((x) => {
|
|
2829
|
+
const [dx, dy] = field(x, y, value / extreme);
|
|
2830
|
+
return [
|
|
2831
|
+
roundTo(weights[r] * dx, 0.01),
|
|
2832
|
+
roundTo(weights[r] * dy, 0.01)
|
|
2833
|
+
];
|
|
2834
|
+
})
|
|
2835
|
+
)
|
|
2836
|
+
}))
|
|
2837
|
+
};
|
|
2838
|
+
};
|
|
2839
|
+
const deformer = {
|
|
2840
|
+
kind: "warp",
|
|
2841
|
+
id: BODY_WARP_ID,
|
|
2842
|
+
grid: {
|
|
2843
|
+
cols: xs.length - 1,
|
|
2844
|
+
rows: ys.length - 1,
|
|
2845
|
+
points: ys.flatMap((y) => xs.flatMap((x) => [x, y]))
|
|
2846
|
+
},
|
|
2847
|
+
warps: [
|
|
2848
|
+
warp(P3.BodyAngleX, BODY_STOPS, (x, _y, t) => [turn(x, t), 0]),
|
|
2849
|
+
warp(P3.BodyAngleY, BODY_STOPS, (_x, _y, t) => [0, t * BODY.bow * hh]),
|
|
2850
|
+
warp(
|
|
2851
|
+
P3.BodyAngleZ,
|
|
2852
|
+
BODY_STOPS,
|
|
2853
|
+
(x, y, t) => roll(x, y, -t * BODY.rollDeg)
|
|
2854
|
+
),
|
|
2855
|
+
warp(P3.AngleX, X_STOPS, (x, _y, t) => [BODY.followX * turn(x, t), 0]),
|
|
2856
|
+
warp(P3.AngleZ, Z_STOPS, (x, y, t) => roll(x, y, -t * BODY.followRoll)),
|
|
2857
|
+
warp(P3.Breath, [0, 1], (_x, _y, t) => [0, t * AMPLITUDE.breathBody * hh])
|
|
2858
|
+
]
|
|
2859
|
+
};
|
|
2860
|
+
refuseFold(body, deformer, anchor, [chin, ...pivots]);
|
|
2861
|
+
return { deformer, mesh: bodyMesh(b, ys) };
|
|
2862
|
+
}
|
|
2863
|
+
function refuseFold(body, d, anchor, pivots) {
|
|
2864
|
+
const { cols, rows, points } = d.grid;
|
|
2865
|
+
const warps = d.warps ?? [];
|
|
2866
|
+
const stride = cols + 1;
|
|
2867
|
+
for (let r = 0; r < rows; r++) {
|
|
2868
|
+
for (let c = 0; c < cols; c++) {
|
|
2869
|
+
const tl = r * stride + c;
|
|
2870
|
+
const [tr, bl, br] = [tl + 1, tl + stride, tl + stride + 1];
|
|
2871
|
+
const corners = [
|
|
2872
|
+
[bl, br, tl],
|
|
2873
|
+
[br, tr, bl],
|
|
2874
|
+
[tr, tl, br],
|
|
2875
|
+
[tl, bl, tr]
|
|
2876
|
+
];
|
|
2877
|
+
for (const [p, q, s] of corners) {
|
|
2878
|
+
const fold = leastArea(points, warps, p, q, s);
|
|
2879
|
+
if (fold.least >= FOLD_MARGIN * fold.rest) continue;
|
|
2880
|
+
const lowest = roundTo(Math.min(...pivots.map((v) => v.y)), 0.01);
|
|
2881
|
+
const pose = warps.map((w, i) => `${w.parameter} ${w.keyforms[fold.stops[i]].value}`).join(", ");
|
|
2882
|
+
throw new LayerGeometryError(
|
|
2883
|
+
`auto-rig: layer "${body.fileName}": the body is too short between its hips (y ${anchor}) and the lowest of the head's and shoulders' pivots (y ${lowest}) for its motion: its body warp's cell from x ${points[tl * 2]} to ${points[tr * 2]}, y ${points[tl * 2 + 1]} to ${points[bl * 2 + 1]}, would fold at or near ${pose}`
|
|
2884
|
+
);
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
function leastArea(points, warps, p, q, s) {
|
|
2890
|
+
const moves = warps.map(
|
|
2891
|
+
(w) => w.keyforms.map(({ offsets: o }) => [
|
|
2892
|
+
o[q * 2] - o[p * 2],
|
|
2893
|
+
o[q * 2 + 1] - o[p * 2 + 1],
|
|
2894
|
+
o[s * 2] - o[p * 2],
|
|
2895
|
+
o[s * 2 + 1] - o[p * 2 + 1]
|
|
2896
|
+
])
|
|
2897
|
+
);
|
|
2898
|
+
const cross = (e) => e[0] * e[3] - e[1] * e[2];
|
|
2899
|
+
const edges = [
|
|
2900
|
+
points[q * 2] - points[p * 2],
|
|
2901
|
+
points[q * 2 + 1] - points[p * 2 + 1],
|
|
2902
|
+
points[s * 2] - points[p * 2],
|
|
2903
|
+
points[s * 2 + 1] - points[p * 2 + 1]
|
|
2904
|
+
];
|
|
2905
|
+
let least = Infinity;
|
|
2906
|
+
let stops = [];
|
|
2907
|
+
const at = [];
|
|
2908
|
+
const walk = (w, ax, ay, bx, by) => {
|
|
2909
|
+
if (w === moves.length) {
|
|
2910
|
+
const area = ax * by - ay * bx;
|
|
2911
|
+
if (area < least) [least, stops] = [area, [...at]];
|
|
2912
|
+
return;
|
|
2913
|
+
}
|
|
2914
|
+
moves[w].forEach(([mx, my, nx, ny], k) => {
|
|
2915
|
+
at[w] = k;
|
|
2916
|
+
walk(w + 1, ax + mx, ay + my, bx + nx, by + ny);
|
|
2917
|
+
});
|
|
2918
|
+
};
|
|
2919
|
+
walk(0, edges[0], edges[1], edges[2], edges[3]);
|
|
2920
|
+
for (const m of moves) {
|
|
2921
|
+
let most = 0;
|
|
2922
|
+
for (let k = 1; k < m.length; k++) {
|
|
2923
|
+
most = Math.max(most, cross(m[k].map((v, i) => v - m[k - 1][i])));
|
|
2924
|
+
}
|
|
2925
|
+
least -= most / 4;
|
|
2926
|
+
}
|
|
2927
|
+
return { rest: cross(edges), least, stops };
|
|
2928
|
+
}
|
|
2929
|
+
function headOwnRoll(hasBody) {
|
|
2930
|
+
return hasBody ? ROLL_DEG - BODY.followRoll : ROLL_DEG;
|
|
2931
|
+
}
|
|
2932
|
+
function headOwnBreath(hasBody) {
|
|
2933
|
+
return hasBody ? AMPLITUDE.breathHead - AMPLITUDE.breathBody : AMPLITUDE.breathHead;
|
|
2934
|
+
}
|
|
2935
|
+
|
|
2936
|
+
// src/auto-rig/arms.ts
|
|
2937
|
+
var ELBOW_AT = 0.42;
|
|
2938
|
+
var CAP_ROWS = [0.1, 0.3];
|
|
2939
|
+
var CAP_SEGMENTS = 12;
|
|
2940
|
+
var ARM_RANGE = [-30, 150];
|
|
2941
|
+
var ARM_PARAMS = {
|
|
2942
|
+
arm_L: { arm: P4.ArmLeft, elbow: P4.ElbowLeft },
|
|
2943
|
+
arm_R: { arm: P4.ArmRight, elbow: P4.ElbowRight }
|
|
2944
|
+
};
|
|
2945
|
+
var ARM_IDS = {
|
|
2946
|
+
arm_L: {
|
|
2947
|
+
forearm: "forearm_L",
|
|
2948
|
+
armDeformer: "armDeformer_L",
|
|
2949
|
+
forearmDeformer: "forearmDeformer_L"
|
|
2950
|
+
},
|
|
2951
|
+
arm_R: {
|
|
2952
|
+
forearm: "forearm_R",
|
|
2953
|
+
armDeformer: "armDeformer_R",
|
|
2954
|
+
forearmDeformer: "forearmDeformer_R"
|
|
2955
|
+
}
|
|
2956
|
+
};
|
|
2957
|
+
function armGeometry(layer, bodyAxisX) {
|
|
2958
|
+
const { bbox, canvasW, canvasH, rowRuns } = layer;
|
|
2959
|
+
const b = boxOfLayer(layer);
|
|
2960
|
+
const span = (k) => {
|
|
2961
|
+
const runs = rowRuns?.[k] ?? [];
|
|
2962
|
+
if (runs.length === 0) return [bbox.x, bbox.x + bbox.w];
|
|
2963
|
+
let [s2, e2] = [runs[0], runs[1]];
|
|
2964
|
+
for (let i = 2; i < runs.length; i += 2) {
|
|
2965
|
+
if (runs[i + 1] - runs[i] > e2 - s2) [s2, e2] = [runs[i], runs[i + 1]];
|
|
2966
|
+
}
|
|
2967
|
+
return [s2, e2];
|
|
2968
|
+
};
|
|
2969
|
+
const centreX = (k) => {
|
|
2970
|
+
const [s2, e2] = span(k);
|
|
2971
|
+
return (s2 + e2) / 2 - canvasW / 2;
|
|
2972
|
+
};
|
|
2973
|
+
const rowY = (k) => canvasH / 2 - (bbox.y + k + 0.5);
|
|
2974
|
+
const painted = rowRuns?.flatMap((runs, k) => runs.length > 0 ? [k] : []) ?? [];
|
|
2975
|
+
const first = painted[0] ?? 0;
|
|
2976
|
+
const last = painted.at(-1) ?? bbox.h - 1;
|
|
2977
|
+
const lo = Math.ceil(CAP_ROWS[0] * bbox.h);
|
|
2978
|
+
const hi = Math.max(lo, Math.floor(CAP_ROWS[1] * bbox.h));
|
|
2979
|
+
const widths = [];
|
|
2980
|
+
for (let k = lo; k <= hi; k++) {
|
|
2981
|
+
const [s2, e2] = span(k);
|
|
2982
|
+
widths.push(e2 - s2);
|
|
2983
|
+
}
|
|
2984
|
+
widths.sort((p, q) => p - q);
|
|
2985
|
+
const ru = widths[widths.length >> 1] / 2;
|
|
2986
|
+
const ks = first + ru;
|
|
2987
|
+
const shoulder = {
|
|
2988
|
+
x: roundTo(centreX(Math.round(ks)), 0.01),
|
|
2989
|
+
y: roundTo(rowY(ks), 0.01)
|
|
2990
|
+
};
|
|
2991
|
+
const ke = Math.round(ks + ELBOW_AT * (last - ks));
|
|
2992
|
+
if (ke <= ks) {
|
|
2993
|
+
throw new LayerGeometryError(
|
|
2994
|
+
`auto-rig: layer "${layer.fileName}": the arm is too short for its width to place a shoulder above an elbow (draw the arm hanging, shoulder at the top)`
|
|
2995
|
+
);
|
|
2996
|
+
}
|
|
2997
|
+
const elbow = { x: roundTo(centreX(ke), 0.01), y: roundTo(rowY(ke), 0.01) };
|
|
2998
|
+
const [s, e] = span(ke);
|
|
2999
|
+
const capRadius = Math.min(
|
|
3000
|
+
(e - s) / 2 + 1,
|
|
3001
|
+
elbow.x - b.x0,
|
|
3002
|
+
b.x1 - elbow.x,
|
|
3003
|
+
b.y1 - elbow.y
|
|
3004
|
+
);
|
|
3005
|
+
return {
|
|
3006
|
+
shoulder,
|
|
3007
|
+
shoulderRadius: ru,
|
|
3008
|
+
elbow,
|
|
3009
|
+
capRadius,
|
|
3010
|
+
side: shoulder.x > bodyAxisX ? 1 : -1
|
|
3011
|
+
};
|
|
3012
|
+
}
|
|
3013
|
+
function meshOf(vertices, indices) {
|
|
3014
|
+
const uvs = vertices.map(
|
|
3015
|
+
(v, i) => roundTo(i % 2 === 0 ? v + 0.5 : 0.5 - v, 1e-5)
|
|
3016
|
+
);
|
|
3017
|
+
return { vertices, uvs, indices };
|
|
3018
|
+
}
|
|
3019
|
+
function armParts(role, layer, g, order) {
|
|
3020
|
+
const b = boxOfLayer(layer);
|
|
3021
|
+
const lx = (x) => roundTo((x - cx(b)) / bw(b), 1e-5);
|
|
3022
|
+
const ly = (y) => roundTo((y - cy(b)) / bh(b), 1e-5);
|
|
3023
|
+
const seam = ly(g.elbow.y);
|
|
3024
|
+
const band = (top, bottom) => [
|
|
3025
|
+
-0.5,
|
|
3026
|
+
top,
|
|
3027
|
+
0.5,
|
|
3028
|
+
top,
|
|
3029
|
+
-0.5,
|
|
3030
|
+
bottom,
|
|
3031
|
+
0.5,
|
|
3032
|
+
bottom
|
|
3033
|
+
];
|
|
3034
|
+
const quad = [2, 3, 0, 0, 3, 1];
|
|
3035
|
+
const cap = [lx(g.elbow.x), seam];
|
|
3036
|
+
const fan = [];
|
|
3037
|
+
for (let i = 0; i <= CAP_SEGMENTS; i++) {
|
|
3038
|
+
const a = Math.PI * i / CAP_SEGMENTS;
|
|
3039
|
+
cap.push(
|
|
3040
|
+
lx(g.elbow.x + g.capRadius * Math.cos(a)),
|
|
3041
|
+
ly(g.elbow.y + g.capRadius * Math.sin(a))
|
|
3042
|
+
);
|
|
3043
|
+
if (i > 0) fan.push(4, 4 + i, 5 + i);
|
|
3044
|
+
}
|
|
3045
|
+
const part = (id, deformer, at, mesh) => ({
|
|
3046
|
+
id,
|
|
3047
|
+
color: [1, 1, 1, 1],
|
|
3048
|
+
width: bw(b),
|
|
3049
|
+
height: bh(b),
|
|
3050
|
+
transform: { x: cx(b), y: cy(b) },
|
|
3051
|
+
order: at,
|
|
3052
|
+
deformer,
|
|
3053
|
+
mesh
|
|
3054
|
+
});
|
|
3055
|
+
const ids = ARM_IDS[role];
|
|
3056
|
+
return [
|
|
3057
|
+
part(role, ids.armDeformer, order, meshOf(band(0.5, seam), quad)),
|
|
3058
|
+
part(
|
|
3059
|
+
ids.forearm,
|
|
3060
|
+
ids.forearmDeformer,
|
|
3061
|
+
order + 1,
|
|
3062
|
+
meshOf([...band(seam, -0.5), ...cap], [...quad, ...fan])
|
|
3063
|
+
)
|
|
3064
|
+
];
|
|
3065
|
+
}
|
|
3066
|
+
function armDeformers(role, g) {
|
|
3067
|
+
const ids = ARM_IDS[role];
|
|
3068
|
+
const params = ARM_PARAMS[role];
|
|
3069
|
+
const rotate = (parameter) => ({
|
|
3070
|
+
parameter,
|
|
3071
|
+
channel: "rotate",
|
|
3072
|
+
from: g.side * ARM_RANGE[0],
|
|
3073
|
+
to: g.side * ARM_RANGE[1]
|
|
3074
|
+
});
|
|
3075
|
+
return [
|
|
3076
|
+
{
|
|
3077
|
+
id: ids.armDeformer,
|
|
3078
|
+
parent: BODY_WARP_ID,
|
|
3079
|
+
pivot: { ...g.shoulder },
|
|
3080
|
+
bindings: [rotate(params.arm)]
|
|
3081
|
+
},
|
|
3082
|
+
{
|
|
3083
|
+
id: ids.forearmDeformer,
|
|
3084
|
+
parent: ids.armDeformer,
|
|
3085
|
+
pivot: { ...g.elbow },
|
|
3086
|
+
bindings: [rotate(params.elbow)]
|
|
3087
|
+
}
|
|
3088
|
+
];
|
|
3089
|
+
}
|
|
3090
|
+
|
|
2363
3091
|
// src/auto-rig/checks.ts
|
|
2364
3092
|
var finite = (v) => typeof v === "number" && Number.isFinite(v);
|
|
2365
3093
|
var isObject = (v) => typeof v === "object" && v !== null;
|
|
@@ -2463,6 +3191,7 @@ function checkLayers(layers, canvas) {
|
|
|
2463
3191
|
if (missing.length > 0) {
|
|
2464
3192
|
throw new Error(`auto-rig: missing required role(s) ${missing.join(", ")}`);
|
|
2465
3193
|
}
|
|
3194
|
+
checkArmsHaveBody(seen);
|
|
2466
3195
|
}
|
|
2467
3196
|
function checkHeadEdges(edges, byRole) {
|
|
2468
3197
|
if (edges === void 0) return;
|
|
@@ -2545,129 +3274,43 @@ function checkStrandEdges(strand, irises, hair) {
|
|
|
2545
3274
|
["irisInner", inner]
|
|
2546
3275
|
]) {
|
|
2547
3276
|
if (x < iris.x0 || x > iris.x1) {
|
|
2548
|
-
throw new TurnTargetError(`${at}.${k} ${x} is off its iris's crop`);
|
|
2549
|
-
}
|
|
2550
|
-
}
|
|
2551
|
-
if (sign * (outer - inner) <= 0) {
|
|
2552
|
-
throw new TurnTargetError(
|
|
2553
|
-
`${at}.irisOuter ${outer} is not strictly outward of irisInner ${inner}`
|
|
2554
|
-
);
|
|
2555
|
-
}
|
|
2556
|
-
for (const [k, x] of [
|
|
2557
|
-
["runOuter", runOuter],
|
|
2558
|
-
["runFace", runFace]
|
|
2559
|
-
]) {
|
|
2560
|
-
if (x !== null && (x < hair.x0 || x > hair.x1)) {
|
|
2561
|
-
throw new TurnTargetError(`${at}.${k} ${x} is off hair_front's crop`);
|
|
2562
|
-
}
|
|
2563
|
-
}
|
|
2564
|
-
if (sign * (runOuter - cx(iris)) <= 0) {
|
|
2565
|
-
throw new TurnTargetError(
|
|
2566
|
-
`${at}.runOuter ${runOuter} is not strictly outward of its iris's centre`
|
|
2567
|
-
);
|
|
2568
|
-
}
|
|
2569
|
-
if (runFace !== null && sign * (runOuter - runFace) <= 0) {
|
|
2570
|
-
throw new TurnTargetError(
|
|
2571
|
-
`${at}.runOuter ${runOuter} is not strictly outward of runFace ${runFace}`
|
|
2572
|
-
);
|
|
2573
|
-
}
|
|
2574
|
-
if (runFace !== null && sign * (runFace - cx(other)) <= 0) {
|
|
2575
|
-
throw new TurnTargetError(
|
|
2576
|
-
`${at}.runFace ${runFace} reaches the other iris's centre; a run that does is a fringe, runFace null`
|
|
2577
|
-
);
|
|
2578
|
-
}
|
|
2579
|
-
}
|
|
2580
|
-
}
|
|
2581
|
-
|
|
2582
|
-
// src/auto-rig/context.ts
|
|
2583
|
-
import { StandardParameter as P2 } from "@ikijs/format";
|
|
2584
|
-
|
|
2585
|
-
// src/auto-rig/grid.ts
|
|
2586
|
-
var X_STOPS = [-30, 0, 30];
|
|
2587
|
-
var Y_STOPS = [-30, 0, 30];
|
|
2588
|
-
function lattice(box, cell, min, max) {
|
|
2589
|
-
const cols = cellsFor(box.x1 - box.x0, cell, min, max);
|
|
2590
|
-
const rows = cellsFor(box.y1 - box.y0, cell, min, max);
|
|
2591
|
-
const points = [];
|
|
2592
|
-
for (let r = 0; r <= rows; r++) {
|
|
2593
|
-
const y = box.y1 - (box.y1 - box.y0) * r / rows;
|
|
2594
|
-
for (let c = 0; c <= cols; c++) {
|
|
2595
|
-
points.push(
|
|
2596
|
-
roundTo(box.x0 + (box.x1 - box.x0) * c / cols, 0.01),
|
|
2597
|
-
roundTo(y, 0.01)
|
|
2598
|
-
);
|
|
2599
|
-
}
|
|
2600
|
-
}
|
|
2601
|
-
return { box, cols, rows, points };
|
|
2602
|
-
}
|
|
2603
|
-
function bake(l, field) {
|
|
2604
|
-
const out = [];
|
|
2605
|
-
for (const ay of Y_STOPS) {
|
|
2606
|
-
for (const ax of X_STOPS) {
|
|
2607
|
-
const offsets = [];
|
|
2608
|
-
for (let k = 0; k < l.points.length; k += 2) {
|
|
2609
|
-
if (ax === 0 && ay === 0) {
|
|
2610
|
-
offsets.push(0, 0);
|
|
2611
|
-
continue;
|
|
2612
|
-
}
|
|
2613
|
-
const [dx, dy] = field(l.points[k], l.points[k + 1], ax, ay);
|
|
2614
|
-
offsets.push(roundTo(dx, 0.1), roundTo(dy, 0.1));
|
|
3277
|
+
throw new TurnTargetError(`${at}.${k} ${x} is off its iris's crop`);
|
|
2615
3278
|
}
|
|
2616
|
-
out.push(offsets);
|
|
2617
3279
|
}
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
}
|
|
2621
|
-
|
|
2622
|
-
const j = Y_STOPS.indexOf(0);
|
|
2623
|
-
const row = X_STOPS.map((_, i) => keyforms[j * X_STOPS.length + i]);
|
|
2624
|
-
return blendStops(X_STOPS, row, ax);
|
|
2625
|
-
}
|
|
2626
|
-
function blendStops(values, offsets, v) {
|
|
2627
|
-
const x = clamp(v, values[0], values[values.length - 1]);
|
|
2628
|
-
let i = 0;
|
|
2629
|
-
while (i < values.length - 2 && x > values[i + 1]) i++;
|
|
2630
|
-
const t = (x - values[i]) / (values[i + 1] - values[i]);
|
|
2631
|
-
return offsets[i].map((o, k) => o + (offsets[i + 1][k] - o) * t);
|
|
2632
|
-
}
|
|
2633
|
-
function land(l, offsets, x, y) {
|
|
2634
|
-
const { cols, rows, points } = l;
|
|
2635
|
-
const stride = cols + 1;
|
|
2636
|
-
let col = cols - 1;
|
|
2637
|
-
for (let c = 0; c < cols; c++) {
|
|
2638
|
-
if (x < points[(c + 1) * 2]) {
|
|
2639
|
-
col = c;
|
|
2640
|
-
break;
|
|
3280
|
+
if (sign * (outer - inner) <= 0) {
|
|
3281
|
+
throw new TurnTargetError(
|
|
3282
|
+
`${at}.irisOuter ${outer} is not strictly outward of irisInner ${inner}`
|
|
3283
|
+
);
|
|
2641
3284
|
}
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
3285
|
+
for (const [k, x] of [
|
|
3286
|
+
["runOuter", runOuter],
|
|
3287
|
+
["runFace", runFace]
|
|
3288
|
+
]) {
|
|
3289
|
+
if (x !== null && (x < hair.x0 || x > hair.x1)) {
|
|
3290
|
+
throw new TurnTargetError(`${at}.${k} ${x} is off hair_front's crop`);
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
if (sign * (runOuter - cx(iris)) <= 0) {
|
|
3294
|
+
throw new TurnTargetError(
|
|
3295
|
+
`${at}.runOuter ${runOuter} is not strictly outward of its iris's centre`
|
|
3296
|
+
);
|
|
3297
|
+
}
|
|
3298
|
+
if (runFace !== null && sign * (runOuter - runFace) <= 0) {
|
|
3299
|
+
throw new TurnTargetError(
|
|
3300
|
+
`${at}.runOuter ${runOuter} is not strictly outward of runFace ${runFace}`
|
|
3301
|
+
);
|
|
3302
|
+
}
|
|
3303
|
+
if (runFace !== null && sign * (runFace - cx(other)) <= 0) {
|
|
3304
|
+
throw new TurnTargetError(
|
|
3305
|
+
`${at}.runFace ${runFace} reaches the other iris's centre; a run that does is a fringe, runFace null`
|
|
3306
|
+
);
|
|
2648
3307
|
}
|
|
2649
3308
|
}
|
|
2650
|
-
const x0 = points[col * 2];
|
|
2651
|
-
const x1 = points[(col + 1) * 2];
|
|
2652
|
-
const yT = points[row * stride * 2 + 1];
|
|
2653
|
-
const yB = points[(row + 1) * stride * 2 + 1];
|
|
2654
|
-
const s = clamp((x - x0) / (x1 - x0), 0, 1);
|
|
2655
|
-
const t = clamp((yT - y) / (yT - yB), 0, 1);
|
|
2656
|
-
const at = (r, c) => {
|
|
2657
|
-
const k = (r * stride + c) * 2;
|
|
2658
|
-
return [points[k] + offsets[k], points[k + 1] + offsets[k + 1]];
|
|
2659
|
-
};
|
|
2660
|
-
const p00 = at(row, col);
|
|
2661
|
-
const p10 = at(row, col + 1);
|
|
2662
|
-
const p01 = at(row + 1, col);
|
|
2663
|
-
const p11 = at(row + 1, col + 1);
|
|
2664
|
-
const topX = p00[0] + (p10[0] - p00[0]) * s;
|
|
2665
|
-
const topY = p00[1] + (p10[1] - p00[1]) * s;
|
|
2666
|
-
const botX = p01[0] + (p11[0] - p01[0]) * s;
|
|
2667
|
-
const botY = p01[1] + (p11[1] - p01[1]) * s;
|
|
2668
|
-
return [topX + (botX - topX) * t, topY + (botY - topY) * t];
|
|
2669
3309
|
}
|
|
2670
3310
|
|
|
3311
|
+
// src/auto-rig/context.ts
|
|
3312
|
+
import { StandardParameter as P5 } from "@ikijs/format";
|
|
3313
|
+
|
|
2671
3314
|
// src/auto-rig/head.ts
|
|
2672
3315
|
function outerEdge(runs, axisX, side) {
|
|
2673
3316
|
if (runs.length === 0) return void 0;
|
|
@@ -3697,6 +4340,7 @@ function familyField(m, family) {
|
|
|
3697
4340
|
case "hair_back":
|
|
3698
4341
|
return hairField(m, m.hairBack, NOD.hairBack);
|
|
3699
4342
|
case "body":
|
|
4343
|
+
case "arm":
|
|
3700
4344
|
return () => [0, 0];
|
|
3701
4345
|
}
|
|
3702
4346
|
}
|
|
@@ -3796,7 +4440,7 @@ function renderedLander(turn, parts, grids, from = /* @__PURE__ */ new Map()) {
|
|
|
3796
4440
|
const family = roleSpec(role).family;
|
|
3797
4441
|
const part = parts.find((p) => p.id === role);
|
|
3798
4442
|
const g = grids.get(family);
|
|
3799
|
-
const warp = part?.warps?.find((w) => w.parameter ===
|
|
4443
|
+
const warp = part?.warps?.find((w) => w.parameter === P5.AngleX);
|
|
3800
4444
|
if (part === void 0 || part.mesh === void 0 || !g && !warp) {
|
|
3801
4445
|
return x + field(role)(x, y, ax, 0)[0];
|
|
3802
4446
|
}
|
|
@@ -4505,179 +5149,6 @@ function buildTurn(frame, landmarks, q) {
|
|
|
4505
5149
|
};
|
|
4506
5150
|
}
|
|
4507
5151
|
|
|
4508
|
-
// src/auto-rig/drivers.ts
|
|
4509
|
-
import {
|
|
4510
|
-
StandardParameter as P3
|
|
4511
|
-
} from "@ikijs/format";
|
|
4512
|
-
var SWAY_RANGE = 20;
|
|
4513
|
-
var SWAY_CURVE = 1.4;
|
|
4514
|
-
var HAIR_HANG = 0.35;
|
|
4515
|
-
var Z_STOPS = [-30, 0, 30];
|
|
4516
|
-
function localWarp(parameter, mesh, box, values, f) {
|
|
4517
|
-
const pts = meshPoints(mesh, box);
|
|
4518
|
-
const w = bw(box);
|
|
4519
|
-
const h = bh(box);
|
|
4520
|
-
return {
|
|
4521
|
-
parameter,
|
|
4522
|
-
keyforms: values.map((value) => ({
|
|
4523
|
-
value,
|
|
4524
|
-
offsets: pts.flatMap((p, i) => {
|
|
4525
|
-
const [dx, dy] = f(p, value, i);
|
|
4526
|
-
return [roundTo(dx / w, 1e-4), roundTo(dy / h, 1e-4)];
|
|
4527
|
-
})
|
|
4528
|
-
}))
|
|
4529
|
-
};
|
|
4530
|
-
}
|
|
4531
|
-
var rollOf = (v) => -v / 30 * ROLL_DEG * DEG;
|
|
4532
|
-
function motionReach(warps, mesh, box, extra = [0, 0]) {
|
|
4533
|
-
const out = {
|
|
4534
|
-
left: extra[0],
|
|
4535
|
-
right: extra[0],
|
|
4536
|
-
bottom: extra[1],
|
|
4537
|
-
top: extra[1]
|
|
4538
|
-
};
|
|
4539
|
-
if (mesh === void 0) return out;
|
|
4540
|
-
const pts = meshPoints(mesh, box);
|
|
4541
|
-
for (const warp of warps ?? []) {
|
|
4542
|
-
let l = 0;
|
|
4543
|
-
let r = 0;
|
|
4544
|
-
let b = 0;
|
|
4545
|
-
let t = 0;
|
|
4546
|
-
for (const k of warp.keyforms) {
|
|
4547
|
-
pts.forEach(([x, y], i) => {
|
|
4548
|
-
const px = x + k.offsets[2 * i] * bw(box);
|
|
4549
|
-
const py = y + k.offsets[2 * i + 1] * bh(box);
|
|
4550
|
-
l = Math.max(l, box.x0 - px);
|
|
4551
|
-
r = Math.max(r, px - box.x1);
|
|
4552
|
-
b = Math.max(b, box.y0 - py);
|
|
4553
|
-
t = Math.max(t, py - box.y1);
|
|
4554
|
-
});
|
|
4555
|
-
}
|
|
4556
|
-
out.left += l;
|
|
4557
|
-
out.right += r;
|
|
4558
|
-
out.bottom += b;
|
|
4559
|
-
out.top += t;
|
|
4560
|
-
}
|
|
4561
|
-
return out;
|
|
4562
|
-
}
|
|
4563
|
-
function blinkFold(param, mesh, box, travel, eye = box) {
|
|
4564
|
-
const yc = eye.y1 - travel * bh(eye);
|
|
4565
|
-
return localWarp(
|
|
4566
|
-
param,
|
|
4567
|
-
mesh,
|
|
4568
|
-
box,
|
|
4569
|
-
[0, 1],
|
|
4570
|
-
([, y], v) => v === 1 ? [0, 0] : [0, yc - y]
|
|
4571
|
-
);
|
|
4572
|
-
}
|
|
4573
|
-
function lashFold(param, mesh, lash, eye, travel) {
|
|
4574
|
-
const yc = eye.y1 - travel * bh(eye) - 0.04 * bh(eye);
|
|
4575
|
-
const c = cx(lash);
|
|
4576
|
-
const half = bw(lash) / 2;
|
|
4577
|
-
const sag = 0.3 * bh(lash);
|
|
4578
|
-
return localWarp(param, mesh, lash, [0, 1], ([x, y], v) => {
|
|
4579
|
-
if (v === 1) return [0, 0];
|
|
4580
|
-
const u = (x - c) / half;
|
|
4581
|
-
return [0, yc + (y - lash.y0) * 0.5 - sag * (1 - u * u) - y];
|
|
4582
|
-
});
|
|
4583
|
-
}
|
|
4584
|
-
function gazeBindings(irisW, share = 1) {
|
|
4585
|
-
const gx = AMPLITUDE.gazeX * irisW * share;
|
|
4586
|
-
const gy = AMPLITUDE.gazeY * irisW * share;
|
|
4587
|
-
return [
|
|
4588
|
-
{
|
|
4589
|
-
parameter: P3.EyeballX,
|
|
4590
|
-
channel: "translateX",
|
|
4591
|
-
from: -round1(gx),
|
|
4592
|
-
to: round1(gx)
|
|
4593
|
-
},
|
|
4594
|
-
{
|
|
4595
|
-
parameter: P3.EyeballY,
|
|
4596
|
-
channel: "translateY",
|
|
4597
|
-
from: -round1(gy),
|
|
4598
|
-
to: round1(gy)
|
|
4599
|
-
}
|
|
4600
|
-
];
|
|
4601
|
-
}
|
|
4602
|
-
function gazeReach(irisW, share = 1) {
|
|
4603
|
-
return [AMPLITUDE.gazeX * irisW * share, AMPLITUDE.gazeY * irisW * share];
|
|
4604
|
-
}
|
|
4605
|
-
function browReach(hh) {
|
|
4606
|
-
return AMPLITUDE.brow * hh;
|
|
4607
|
-
}
|
|
4608
|
-
function browBindings(side, reach) {
|
|
4609
|
-
const y = side === "L" ? P3.BrowLeftY : P3.BrowRightY;
|
|
4610
|
-
const a = side === "L" ? P3.BrowLeftAngle : P3.BrowRightAngle;
|
|
4611
|
-
return [
|
|
4612
|
-
{
|
|
4613
|
-
parameter: y,
|
|
4614
|
-
channel: "translateY",
|
|
4615
|
-
from: -round1(reach),
|
|
4616
|
-
to: round1(reach)
|
|
4617
|
-
},
|
|
4618
|
-
{ parameter: a, channel: "rotate", from: -BROW_TILT, to: BROW_TILT }
|
|
4619
|
-
];
|
|
4620
|
-
}
|
|
4621
|
-
var BROW_TILT = 15;
|
|
4622
|
-
function mouthForm(mesh, box) {
|
|
4623
|
-
const a = 0.3 * bh(box);
|
|
4624
|
-
const c = cx(box);
|
|
4625
|
-
const half = bw(box) / 2;
|
|
4626
|
-
return localWarp(P3.MouthForm, mesh, box, [-1, 0, 1], ([x], v) => {
|
|
4627
|
-
if (v === 0) return [0, 0];
|
|
4628
|
-
const u = (x - c) / half;
|
|
4629
|
-
const widen = v > 0 ? 0.05 : 0.03;
|
|
4630
|
-
return [v * widen * (x - c), v * a * (u * u - 0.3)];
|
|
4631
|
-
});
|
|
4632
|
-
}
|
|
4633
|
-
function mouthOpenGrow(mesh, box) {
|
|
4634
|
-
const c = cx(box);
|
|
4635
|
-
return localWarp(
|
|
4636
|
-
P3.MouthOpen,
|
|
4637
|
-
mesh,
|
|
4638
|
-
box,
|
|
4639
|
-
[0, 1],
|
|
4640
|
-
([x, y], v) => v === 1 ? [(AMPLITUDE.mouthOpenWidth - 1) * (x - c), 0] : [0, box.y1 + (y - box.y1) * 0.2 - y]
|
|
4641
|
-
);
|
|
4642
|
-
}
|
|
4643
|
-
function mouthWiden(mesh, box) {
|
|
4644
|
-
const c = cx(box);
|
|
4645
|
-
return localWarp(P3.MouthOpen, mesh, box, [0, 1], ([x], v) => [
|
|
4646
|
-
v * (AMPLITUDE.mouthOpenWidth - 1) * (x - c),
|
|
4647
|
-
0
|
|
4648
|
-
]);
|
|
4649
|
-
}
|
|
4650
|
-
function hairSway(param, mesh, box, amp) {
|
|
4651
|
-
return localWarp(
|
|
4652
|
-
param,
|
|
4653
|
-
mesh,
|
|
4654
|
-
box,
|
|
4655
|
-
[-SWAY_RANGE, 0, SWAY_RANGE],
|
|
4656
|
-
([, y], v) => {
|
|
4657
|
-
const t = Math.max(0, (box.y1 - y) / bh(box));
|
|
4658
|
-
return [v / SWAY_RANGE * amp * t ** SWAY_CURVE, 0];
|
|
4659
|
-
}
|
|
4660
|
-
);
|
|
4661
|
-
}
|
|
4662
|
-
function tiltHang(mesh, box, pivot, weight) {
|
|
4663
|
-
return localWarp(P3.AngleZ, mesh, box, Z_STOPS, ([x, y], v, i) => {
|
|
4664
|
-
const g = weight(y, i);
|
|
4665
|
-
if (g === 0 || v === 0) return [0, 0];
|
|
4666
|
-
const a = -g * rollOf(v);
|
|
4667
|
-
const px = x - pivot[0];
|
|
4668
|
-
const py = y - pivot[1];
|
|
4669
|
-
return [
|
|
4670
|
-
pivot[0] + px * Math.cos(a) - py * Math.sin(a) - x,
|
|
4671
|
-
pivot[1] + px * Math.sin(a) + py * Math.cos(a) - y
|
|
4672
|
-
];
|
|
4673
|
-
});
|
|
4674
|
-
}
|
|
4675
|
-
function hairHangWeight(pivotY, bottom) {
|
|
4676
|
-
if (bottom >= pivotY) return () => 0;
|
|
4677
|
-
return (y) => HAIR_HANG * smoothstep(pivotY, bottom, y);
|
|
4678
|
-
}
|
|
4679
|
-
var round1 = (v) => roundTo(v, 0.1);
|
|
4680
|
-
|
|
4681
5152
|
// src/auto-rig/generate.ts
|
|
4682
5153
|
var MESH_CELLS = {
|
|
4683
5154
|
face: { px: 28, min: 4, max: 24 },
|
|
@@ -4704,6 +5175,7 @@ var WARP_ID = {
|
|
|
4704
5175
|
mouth: "mouthWarp"
|
|
4705
5176
|
};
|
|
4706
5177
|
var isGridFamily = (f) => f in WARP_ID;
|
|
5178
|
+
var isArm = (spec) => spec.family === "arm";
|
|
4707
5179
|
var GRID_PAD = 6;
|
|
4708
5180
|
function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
4709
5181
|
checkLayers(layers, canvas);
|
|
@@ -4752,6 +5224,18 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
4752
5224
|
chinSlide: chinSlide(fit.model)
|
|
4753
5225
|
});
|
|
4754
5226
|
const turn = { ...fit.model, frame };
|
|
5227
|
+
const arms = /* @__PURE__ */ new Map();
|
|
5228
|
+
for (const spec of ROLE_TABLE) {
|
|
5229
|
+
const layer = byRole.get(spec.role);
|
|
5230
|
+
if (layer === void 0 || !isArm(spec)) continue;
|
|
5231
|
+
arms.set(spec.role, armGeometry(layer, cx(box("body"))));
|
|
5232
|
+
}
|
|
5233
|
+
const bodyWarp = has("body") ? buildBodyWarp({
|
|
5234
|
+
body: byRole.get("body"),
|
|
5235
|
+
chin: { x: frame.axisX, y: frame.chinY },
|
|
5236
|
+
hh: frame.hh,
|
|
5237
|
+
pivots: [...arms.values()].map((g) => g.shoulder)
|
|
5238
|
+
}) : void 0;
|
|
4755
5239
|
const parts = [];
|
|
4756
5240
|
const reach = /* @__PURE__ */ new Map();
|
|
4757
5241
|
let regionOf = [];
|
|
@@ -4759,6 +5243,12 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
4759
5243
|
for (const spec of ROLE_TABLE) {
|
|
4760
5244
|
const layer = byRole.get(spec.role);
|
|
4761
5245
|
if (layer === void 0) continue;
|
|
5246
|
+
if (isArm(spec)) {
|
|
5247
|
+
parts.push(
|
|
5248
|
+
...armParts(spec.role, layer, arms.get(spec.role), parts.length)
|
|
5249
|
+
);
|
|
5250
|
+
continue;
|
|
5251
|
+
}
|
|
4762
5252
|
const built = buildPart(
|
|
4763
5253
|
spec,
|
|
4764
5254
|
boxOfLayer(layer),
|
|
@@ -4777,7 +5267,9 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
4777
5267
|
}
|
|
4778
5268
|
const grids = /* @__PURE__ */ new Map();
|
|
4779
5269
|
for (const family of Object.keys(WARP_ID)) {
|
|
4780
|
-
const members = parts.filter(
|
|
5270
|
+
const members = parts.filter(
|
|
5271
|
+
(p) => roleSpec(roleOfPart(p.id)).family === family
|
|
5272
|
+
);
|
|
4781
5273
|
if (members.length === 0) continue;
|
|
4782
5274
|
const area = unionBoxes(
|
|
4783
5275
|
members.map((p) => {
|
|
@@ -4799,13 +5291,19 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
4799
5291
|
});
|
|
4800
5292
|
}
|
|
4801
5293
|
for (const part of parts) {
|
|
4802
|
-
const family = roleSpec(part.id).family;
|
|
5294
|
+
const family = roleSpec(roleOfPart(part.id)).family;
|
|
4803
5295
|
if (isGridFamily(family)) {
|
|
4804
5296
|
part.deformer = WARP_ID[family];
|
|
4805
5297
|
continue;
|
|
4806
5298
|
}
|
|
4807
|
-
|
|
4808
|
-
|
|
5299
|
+
if (family === "body") {
|
|
5300
|
+
part.deformer = BODY_WARP_ID;
|
|
5301
|
+
part.mesh = bodyWarp.mesh;
|
|
5302
|
+
continue;
|
|
5303
|
+
}
|
|
5304
|
+
if (family === "arm") continue;
|
|
5305
|
+
part.deformer = "headDeformer";
|
|
5306
|
+
if (part.mesh === void 0) continue;
|
|
4809
5307
|
const own = familyField(turn, family);
|
|
4810
5308
|
const fieldOf = part.id === "face" ? plateFields(turn, regionOf, own) : () => own;
|
|
4811
5309
|
part.warps = [
|
|
@@ -4820,7 +5318,7 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
4820
5318
|
canvas: { width: canvas.width, height: canvas.height },
|
|
4821
5319
|
parameters,
|
|
4822
5320
|
parts,
|
|
4823
|
-
deformers: deformers(frame, grids,
|
|
5321
|
+
deformers: deformers(frame, grids, bodyWarp?.deformer, arms),
|
|
4824
5322
|
...has("hair_front") ? { physics: hairPhysics() } : {},
|
|
4825
5323
|
expressions: defaultExpressions(new Set(parameters.map((p) => p.id))),
|
|
4826
5324
|
motions: DEFAULT_MOTIONS
|
|
@@ -4840,14 +5338,14 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
4840
5338
|
function turnWarps(mesh, b, fieldOf) {
|
|
4841
5339
|
return [
|
|
4842
5340
|
localWarp(
|
|
4843
|
-
|
|
5341
|
+
P6.AngleX,
|
|
4844
5342
|
mesh,
|
|
4845
5343
|
b,
|
|
4846
5344
|
X_STOPS,
|
|
4847
5345
|
([x, y], v, i) => v === 0 ? [0, 0] : [fieldOf(i)(x, y, v, 0)[0], 0]
|
|
4848
5346
|
),
|
|
4849
5347
|
localWarp(
|
|
4850
|
-
|
|
5348
|
+
P6.AngleY,
|
|
4851
5349
|
mesh,
|
|
4852
5350
|
b,
|
|
4853
5351
|
Y_STOPS,
|
|
@@ -4895,16 +5393,22 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
4895
5393
|
}
|
|
4896
5394
|
if (mesh !== void 0) part.mesh = mesh;
|
|
4897
5395
|
const side = spec.role.endsWith("_L") ? "L" : "R";
|
|
4898
|
-
const eyeOpen = side === "L" ?
|
|
5396
|
+
const eyeOpen = side === "L" ? P6.EyeOpenLeft : P6.EyeOpenRight;
|
|
4899
5397
|
const hh = frame.hh;
|
|
4900
5398
|
switch (spec.role) {
|
|
4901
5399
|
case "face":
|
|
4902
5400
|
if (region?.some((r) => r === "neck")) {
|
|
4903
5401
|
const isNeck = region.map((r) => r === "neck");
|
|
4904
5402
|
warps.push(
|
|
4905
|
-
tiltHang(
|
|
5403
|
+
tiltHang(
|
|
5404
|
+
mesh,
|
|
5405
|
+
b,
|
|
5406
|
+
pivot,
|
|
5407
|
+
(_y, i) => isNeck[i] ? 1 : 0,
|
|
5408
|
+
headOwnRoll(byRole.has("body"))
|
|
5409
|
+
),
|
|
4906
5410
|
localWarp(
|
|
4907
|
-
|
|
5411
|
+
P6.Breath,
|
|
4908
5412
|
mesh,
|
|
4909
5413
|
b,
|
|
4910
5414
|
[0, 1],
|
|
@@ -4951,8 +5455,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
4951
5455
|
if (byRole.has("mouth_open")) {
|
|
4952
5456
|
warps.push(mouthWiden(mesh, b));
|
|
4953
5457
|
bindings.push(
|
|
4954
|
-
{ parameter:
|
|
4955
|
-
{ parameter:
|
|
5458
|
+
{ parameter: P6.MouthOpen, channel: "opacity", from: 1, to: 0 },
|
|
5459
|
+
{ parameter: P6.MouthOpen, channel: "opacity", from: 1, to: 0 }
|
|
4956
5460
|
);
|
|
4957
5461
|
extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
|
|
4958
5462
|
} else {
|
|
@@ -4962,19 +5466,19 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
4962
5466
|
);
|
|
4963
5467
|
bindings.push(
|
|
4964
5468
|
{
|
|
4965
|
-
parameter:
|
|
5469
|
+
parameter: P6.MouthOpen,
|
|
4966
5470
|
channel: "scaleY",
|
|
4967
5471
|
from: 0,
|
|
4968
5472
|
to: roundTo(grow, 0.01)
|
|
4969
5473
|
},
|
|
4970
5474
|
{
|
|
4971
|
-
parameter:
|
|
5475
|
+
parameter: P6.MouthOpen,
|
|
4972
5476
|
channel: "scaleX",
|
|
4973
5477
|
from: 0,
|
|
4974
5478
|
to: roundTo(AMPLITUDE.mouthOpenWidth - 1, 0.01)
|
|
4975
5479
|
},
|
|
4976
5480
|
{
|
|
4977
|
-
parameter:
|
|
5481
|
+
parameter: P6.MouthOpen,
|
|
4978
5482
|
channel: "translateY",
|
|
4979
5483
|
from: 0,
|
|
4980
5484
|
to: -roundTo(grow * bh(b) / 2, 0.01)
|
|
@@ -4986,7 +5490,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
4986
5490
|
case "mouth_open":
|
|
4987
5491
|
warps.push(mouthForm(mesh, b), mouthOpenGrow(mesh, b));
|
|
4988
5492
|
bindings.push({
|
|
4989
|
-
parameter:
|
|
5493
|
+
parameter: P6.MouthOpen,
|
|
4990
5494
|
channel: "opacity",
|
|
4991
5495
|
from: 0,
|
|
4992
5496
|
to: 1
|
|
@@ -4996,7 +5500,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
4996
5500
|
case "blush_L":
|
|
4997
5501
|
case "blush_R":
|
|
4998
5502
|
bindings.push({
|
|
4999
|
-
parameter:
|
|
5503
|
+
parameter: P6.Cheek,
|
|
5000
5504
|
channel: "opacity",
|
|
5001
5505
|
from: AMPLITUDE.blushRest,
|
|
5002
5506
|
to: 1
|
|
@@ -5007,8 +5511,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5007
5511
|
if (byRole.has("hair_front")) {
|
|
5008
5512
|
const amp = style.sway * AMPLITUDE.sway * hh;
|
|
5009
5513
|
warps.push(
|
|
5010
|
-
hairSway(
|
|
5011
|
-
hairSway(
|
|
5514
|
+
hairSway(P6.HairSwayX, mesh, b, amp),
|
|
5515
|
+
hairSway(P6.HairSwayZ, mesh, b, amp)
|
|
5012
5516
|
);
|
|
5013
5517
|
}
|
|
5014
5518
|
warps.push(tiltHang(mesh, b, pivot, hairHangWeight(frame.chinY, b.y0)));
|
|
@@ -5081,33 +5585,24 @@ function meshScale(role, frame) {
|
|
|
5081
5585
|
return Infinity;
|
|
5082
5586
|
}
|
|
5083
5587
|
}
|
|
5084
|
-
function deformers(frame, grids,
|
|
5588
|
+
function deformers(frame, grids, bodyWarp, arms) {
|
|
5085
5589
|
const out = [];
|
|
5086
|
-
if (
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
bindings: [
|
|
5091
|
-
{
|
|
5092
|
-
parameter: P4.Breath,
|
|
5093
|
-
channel: "translateY",
|
|
5094
|
-
from: 0,
|
|
5095
|
-
to: roundTo(AMPLITUDE.breathBody * frame.hh, 0.01)
|
|
5096
|
-
}
|
|
5097
|
-
]
|
|
5098
|
-
});
|
|
5099
|
-
}
|
|
5590
|
+
if (bodyWarp !== void 0) out.push(bodyWarp);
|
|
5591
|
+
for (const [role, g] of arms) out.push(...armDeformers(role, g));
|
|
5592
|
+
const hasBody = bodyWarp !== void 0;
|
|
5593
|
+
const roll = headOwnRoll(hasBody);
|
|
5100
5594
|
out.push({
|
|
5101
5595
|
id: "headDeformer",
|
|
5596
|
+
...hasBody ? { parent: BODY_WARP_ID } : {},
|
|
5102
5597
|
pivot: { x: roundTo(frame.axisX, 0.01), y: roundTo(frame.chinY, 0.01) },
|
|
5103
5598
|
bindings: [
|
|
5104
5599
|
// AngleZ is clockwise-positive (Live2D's); a rotation is CCW-positive.
|
|
5105
|
-
{ parameter:
|
|
5600
|
+
{ parameter: P6.AngleZ, channel: "rotate", from: roll, to: -roll },
|
|
5106
5601
|
{
|
|
5107
|
-
parameter:
|
|
5602
|
+
parameter: P6.Breath,
|
|
5108
5603
|
channel: "translateY",
|
|
5109
5604
|
from: 0,
|
|
5110
|
-
to: roundTo(
|
|
5605
|
+
to: roundTo(headOwnBreath(hasBody) * frame.hh, 0.01)
|
|
5111
5606
|
}
|
|
5112
5607
|
]
|
|
5113
5608
|
});
|
|
@@ -5122,8 +5617,8 @@ function deformers(frame, grids, body) {
|
|
|
5122
5617
|
points: g.lattice.points
|
|
5123
5618
|
},
|
|
5124
5619
|
warp2d: {
|
|
5125
|
-
parameter:
|
|
5126
|
-
parameterY:
|
|
5620
|
+
parameter: P6.AngleX,
|
|
5621
|
+
parameterY: P6.AngleY,
|
|
5127
5622
|
valuesX: X_STOPS,
|
|
5128
5623
|
valuesY: Y_STOPS,
|
|
5129
5624
|
keyforms2d: g.keyforms.map((offsets) => ({ offsets }))
|
|
@@ -5136,16 +5631,16 @@ function hairPhysics() {
|
|
|
5136
5631
|
return [
|
|
5137
5632
|
{
|
|
5138
5633
|
id: "hairSway",
|
|
5139
|
-
input: { parameter:
|
|
5140
|
-
output: { parameter:
|
|
5634
|
+
input: { parameter: P6.AngleX, weight: 1 },
|
|
5635
|
+
output: { parameter: P6.HairSwayX, scale: 5 },
|
|
5141
5636
|
mass: 1,
|
|
5142
5637
|
stiffness: 30,
|
|
5143
5638
|
damping: 3
|
|
5144
5639
|
},
|
|
5145
5640
|
{
|
|
5146
5641
|
id: "hairTilt",
|
|
5147
|
-
input: { parameter:
|
|
5148
|
-
output: { parameter:
|
|
5642
|
+
input: { parameter: P6.AngleZ, weight: 1 },
|
|
5643
|
+
output: { parameter: P6.HairSwayZ, scale: 8 },
|
|
5149
5644
|
mass: 1,
|
|
5150
5645
|
stiffness: 25,
|
|
5151
5646
|
damping: 2.5
|
|
@@ -5155,32 +5650,46 @@ function hairPhysics() {
|
|
|
5155
5650
|
function declareParameters(roles) {
|
|
5156
5651
|
const out = [];
|
|
5157
5652
|
const add = (id, name, min, max, def) => out.push({ id, name, min, max, default: def });
|
|
5158
|
-
add(
|
|
5159
|
-
add(
|
|
5160
|
-
add(
|
|
5161
|
-
|
|
5162
|
-
|
|
5653
|
+
add(P6.AngleX, "Head Angle", -30, 30, 0);
|
|
5654
|
+
add(P6.AngleY, "Head Angle Y", -30, 30, 0);
|
|
5655
|
+
add(P6.AngleZ, "Head Angle Z", -30, 30, 0);
|
|
5656
|
+
if (roles.has("body")) {
|
|
5657
|
+
add(P6.BodyAngleX, "Body Angle X", -10, 10, 0);
|
|
5658
|
+
add(P6.BodyAngleY, "Body Angle Y", -10, 10, 0);
|
|
5659
|
+
add(P6.BodyAngleZ, "Body Angle Z", -10, 10, 0);
|
|
5660
|
+
}
|
|
5661
|
+
const [armMin, armMax] = ARM_RANGE;
|
|
5662
|
+
if (roles.has("arm_L")) {
|
|
5663
|
+
add(P6.ArmLeft, "Arm L", armMin, armMax, 0);
|
|
5664
|
+
add(P6.ElbowLeft, "Elbow L", armMin, armMax, 0);
|
|
5665
|
+
}
|
|
5666
|
+
if (roles.has("arm_R")) {
|
|
5667
|
+
add(P6.ArmRight, "Arm R", armMin, armMax, 0);
|
|
5668
|
+
add(P6.ElbowRight, "Elbow R", armMin, armMax, 0);
|
|
5669
|
+
}
|
|
5670
|
+
add(P6.EyeOpenLeft, "Eye L", 0, 1, 1);
|
|
5671
|
+
add(P6.EyeOpenRight, "Eye R", 0, 1, 1);
|
|
5163
5672
|
if ([...roles].some((r) => /^(iris|pupil|highlight)_/.test(r))) {
|
|
5164
|
-
add(
|
|
5165
|
-
add(
|
|
5673
|
+
add(P6.EyeballX, "Gaze X", -1, 1, 0);
|
|
5674
|
+
add(P6.EyeballY, "Gaze Y", -1, 1, 0);
|
|
5166
5675
|
}
|
|
5167
|
-
add(
|
|
5168
|
-
add(
|
|
5676
|
+
add(P6.MouthOpen, "Mouth Open", 0, 1, 0);
|
|
5677
|
+
add(P6.MouthForm, "Mouth Form", -1, 1, 0);
|
|
5169
5678
|
if (roles.has("brow_L")) {
|
|
5170
|
-
add(
|
|
5171
|
-
add(
|
|
5679
|
+
add(P6.BrowLeftY, "Brow L Y", -1, 1, 0);
|
|
5680
|
+
add(P6.BrowLeftAngle, "Brow L Angle", -1, 1, 0);
|
|
5172
5681
|
}
|
|
5173
5682
|
if (roles.has("brow_R")) {
|
|
5174
|
-
add(
|
|
5175
|
-
add(
|
|
5683
|
+
add(P6.BrowRightY, "Brow R Y", -1, 1, 0);
|
|
5684
|
+
add(P6.BrowRightAngle, "Brow R Angle", -1, 1, 0);
|
|
5176
5685
|
}
|
|
5177
5686
|
if (roles.has("blush_L") || roles.has("blush_R")) {
|
|
5178
|
-
add(
|
|
5687
|
+
add(P6.Cheek, "Cheek", 0, 1, 0);
|
|
5179
5688
|
}
|
|
5180
|
-
add(
|
|
5689
|
+
add(P6.Breath, "Breath", 0, 1, 0);
|
|
5181
5690
|
if (roles.has("hair_front")) {
|
|
5182
|
-
add(
|
|
5183
|
-
add(
|
|
5691
|
+
add(P6.HairSwayX, "Hair Sway X", -SWAY_RANGE, SWAY_RANGE, 0);
|
|
5692
|
+
add(P6.HairSwayZ, "Hair Sway Z", -SWAY_RANGE, SWAY_RANGE, 0);
|
|
5184
5693
|
}
|
|
5185
5694
|
return out;
|
|
5186
5695
|
}
|
|
@@ -5198,6 +5707,7 @@ export {
|
|
|
5198
5707
|
DeletePhysicsRig,
|
|
5199
5708
|
EditorDocument,
|
|
5200
5709
|
HEAD_BAND,
|
|
5710
|
+
LayerGeometryError,
|
|
5201
5711
|
SPECK_CORE_FRACTION,
|
|
5202
5712
|
SetDeformerBindings,
|
|
5203
5713
|
SetDeformerParent,
|
|
@@ -5232,6 +5742,7 @@ export {
|
|
|
5232
5742
|
isSpeckCore,
|
|
5233
5743
|
packAtlas,
|
|
5234
5744
|
parseLayerRoles,
|
|
5745
|
+
partIdsOfRole,
|
|
5235
5746
|
upsertGridKeyform,
|
|
5236
5747
|
uvRectFor,
|
|
5237
5748
|
validateDeformerDelete,
|