@ikijs/editor 0.16.1 → 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 +38 -18
- package/dist/index.d.ts +38 -18
- package/dist/index.js +860 -357
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +860 -355
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ __export(index_exports, {
|
|
|
33
33
|
DeletePhysicsRig: () => DeletePhysicsRig,
|
|
34
34
|
EditorDocument: () => EditorDocument,
|
|
35
35
|
HEAD_BAND: () => HEAD_BAND,
|
|
36
|
+
LayerGeometryError: () => LayerGeometryError,
|
|
36
37
|
SPECK_CORE_FRACTION: () => SPECK_CORE_FRACTION,
|
|
37
38
|
SetDeformerBindings: () => SetDeformerBindings,
|
|
38
39
|
SetDeformerParent: () => SetDeformerParent,
|
|
@@ -67,6 +68,7 @@ __export(index_exports, {
|
|
|
67
68
|
isSpeckCore: () => isSpeckCore,
|
|
68
69
|
packAtlas: () => packAtlas,
|
|
69
70
|
parseLayerRoles: () => parseLayerRoles,
|
|
71
|
+
partIdsOfRole: () => partIdsOfRole,
|
|
70
72
|
upsertGridKeyform: () => upsertGridKeyform,
|
|
71
73
|
uvRectFor: () => uvRectFor,
|
|
72
74
|
validateDeformerDelete: () => validateDeformerDelete,
|
|
@@ -1632,6 +1634,14 @@ function facePlateHalfOf(layers) {
|
|
|
1632
1634
|
return face.cropW / 2;
|
|
1633
1635
|
}
|
|
1634
1636
|
var STRAND_MIN_RUN_FRACTION = 0.5;
|
|
1637
|
+
var ROW_RUN_ROLES = /* @__PURE__ */ new Set([
|
|
1638
|
+
"face",
|
|
1639
|
+
"hair_front",
|
|
1640
|
+
"hair_back",
|
|
1641
|
+
"body",
|
|
1642
|
+
"arm_L",
|
|
1643
|
+
"arm_R"
|
|
1644
|
+
]);
|
|
1635
1645
|
function createLayerSetMeasurer(canvas) {
|
|
1636
1646
|
for (const field of ["width", "height"]) {
|
|
1637
1647
|
const value = canvas[field];
|
|
@@ -1661,7 +1671,7 @@ function createLayerSetMeasurer(canvas) {
|
|
|
1661
1671
|
if (bbox === null) return null;
|
|
1662
1672
|
const rowLeft = new Int32Array(canvasH).fill(canvasW);
|
|
1663
1673
|
const rowRight = new Int32Array(canvasH).fill(-1);
|
|
1664
|
-
const rowRuns = role
|
|
1674
|
+
const rowRuns = ROW_RUN_ROLES.has(role) ? [] : void 0;
|
|
1665
1675
|
for (let y = 0; y < canvasH; y++) {
|
|
1666
1676
|
let runs;
|
|
1667
1677
|
if (rowRuns !== void 0 && y >= bbox.y && y < bbox.y + bbox.h) {
|
|
@@ -2003,7 +2013,13 @@ var PROFILE_VALUES = {
|
|
|
2003
2013
|
brow: 0.15,
|
|
2004
2014
|
breath: 0.03,
|
|
2005
2015
|
sway: 0.08,
|
|
2006
|
-
blushRest: 0.4
|
|
2016
|
+
blushRest: 0.4,
|
|
2017
|
+
bodySlide: 0.1,
|
|
2018
|
+
bodyNarrow: 0.05,
|
|
2019
|
+
bodyBow: 0.06,
|
|
2020
|
+
bodyRoll: 4,
|
|
2021
|
+
bodyFollowX: 0.3,
|
|
2022
|
+
bodyFollowZ: 0.3
|
|
2007
2023
|
};
|
|
2008
2024
|
function deriveProfile(v) {
|
|
2009
2025
|
const farLead = v.lead * (1 - v.fore / 2);
|
|
@@ -2097,9 +2113,32 @@ function deriveProfile(v) {
|
|
|
2097
2113
|
* slide, to the next 0.05. */
|
|
2098
2114
|
hiddenNeck: Math.ceil(30 * (v.slide + v.lead) - 1e-9) / 20
|
|
2099
2115
|
};
|
|
2100
|
-
|
|
2116
|
+
const body = {
|
|
2117
|
+
/** BodyAngleX: the upper body's slide, hh. */
|
|
2118
|
+
slide: v.bodySlide,
|
|
2119
|
+
/** BodyAngleX: how much narrower the upper body gets, either way. */
|
|
2120
|
+
narrow: v.bodyNarrow,
|
|
2121
|
+
/** BodyAngleY: the upper body's rise (+) or bow (−), hh. */
|
|
2122
|
+
bow: v.bodyBow,
|
|
2123
|
+
/** BodyAngleZ: the upper body's roll about the hips, degrees. */
|
|
2124
|
+
rollDeg: v.bodyRoll,
|
|
2125
|
+
/** AngleX: the body's follow, as a share of BodyAngleX's motion. */
|
|
2126
|
+
followX: v.bodyFollowX,
|
|
2127
|
+
/** AngleZ: the body's follow, as a share of its roll. */
|
|
2128
|
+
followZ: v.bodyFollowZ,
|
|
2129
|
+
/** The roll the follow gives the body at AngleZ ±30, degrees: β, the
|
|
2130
|
+
* share of `ROLL_DEG` the head's own roll leaves to the body. */
|
|
2131
|
+
followRoll: v.bodyFollowZ * v.bodyRoll
|
|
2132
|
+
};
|
|
2133
|
+
return {
|
|
2134
|
+
TURN: turn,
|
|
2135
|
+
NOD: nod,
|
|
2136
|
+
ROLL_DEG: v.rollDeg,
|
|
2137
|
+
AMPLITUDE: amplitude,
|
|
2138
|
+
BODY: body
|
|
2139
|
+
};
|
|
2101
2140
|
}
|
|
2102
|
-
var { TURN, NOD, ROLL_DEG, AMPLITUDE } = deriveProfile(PROFILE_VALUES);
|
|
2141
|
+
var { TURN, NOD, ROLL_DEG, AMPLITUDE, BODY } = deriveProfile(PROFILE_VALUES);
|
|
2103
2142
|
var STYLE_KNOBS = [
|
|
2104
2143
|
"turn",
|
|
2105
2144
|
"featureLead",
|
|
@@ -2129,6 +2168,12 @@ var TurnTargetError = class extends Error {
|
|
|
2129
2168
|
this.name = "TurnTargetError";
|
|
2130
2169
|
}
|
|
2131
2170
|
};
|
|
2171
|
+
var LayerGeometryError = class extends Error {
|
|
2172
|
+
constructor(message) {
|
|
2173
|
+
super(message);
|
|
2174
|
+
this.name = "LayerGeometryError";
|
|
2175
|
+
}
|
|
2176
|
+
};
|
|
2132
2177
|
var DEFAULT_TURN_TARGETS = Object.freeze({
|
|
2133
2178
|
eyeShift: round2((TURN.eyeFar + TURN.eyeNear) / 2 - TURN.hairBack),
|
|
2134
2179
|
farEyeRatio: round2(TURN.eyeFarScale / TURN.eyeNearScale),
|
|
@@ -2139,6 +2184,8 @@ var DEFAULT_TURN_TARGETS = Object.freeze({
|
|
|
2139
2184
|
var ROLE_TABLE = [
|
|
2140
2185
|
{ role: "hair_back", family: "hair_back" },
|
|
2141
2186
|
{ role: "body", family: "body" },
|
|
2187
|
+
{ role: "arm_L", family: "arm", parts: ["arm_L", "forearm_L"] },
|
|
2188
|
+
{ role: "arm_R", family: "arm", parts: ["arm_R", "forearm_R"] },
|
|
2142
2189
|
{ role: "face", family: "face", required: true },
|
|
2143
2190
|
{ role: "blush_L", family: "face" },
|
|
2144
2191
|
{ role: "blush_R", family: "face" },
|
|
@@ -2170,6 +2217,15 @@ function roleSpec(role) {
|
|
|
2170
2217
|
if (spec === void 0) throw new Error(`auto-rig: unknown role "${role}"`);
|
|
2171
2218
|
return spec;
|
|
2172
2219
|
}
|
|
2220
|
+
function partIdsOfRole(role) {
|
|
2221
|
+
return roleSpec(role).parts ?? [role];
|
|
2222
|
+
}
|
|
2223
|
+
var ROLE_BY_PART = new Map(
|
|
2224
|
+
ROLE_TABLE.flatMap((r) => (r.parts ?? []).map((p) => [p, r.role]))
|
|
2225
|
+
);
|
|
2226
|
+
function roleOfPart(partId) {
|
|
2227
|
+
return ROLE_BY_PART.get(partId) ?? partId;
|
|
2228
|
+
}
|
|
2173
2229
|
var ALIASES = {
|
|
2174
2230
|
eyebrow_L: "brow_L",
|
|
2175
2231
|
eyebrow_R: "brow_R",
|
|
@@ -2180,6 +2236,16 @@ function roleOf(fileName) {
|
|
|
2180
2236
|
const role = fileName.replace(/\.[^.]+$/, "").toLowerCase().replace(/[-\s]+/g, "_").replace(/_([lr])$/, (_, s) => `_${s.toUpperCase()}`);
|
|
2181
2237
|
return ALIASES[role] ?? role;
|
|
2182
2238
|
}
|
|
2239
|
+
function checkArmsHaveBody(roles) {
|
|
2240
|
+
const has = new Set(roles);
|
|
2241
|
+
if (has.has("body")) return;
|
|
2242
|
+
const arm = ROLE_TABLE.find((r) => r.family === "arm" && has.has(r.role));
|
|
2243
|
+
if (arm !== void 0) {
|
|
2244
|
+
throw new Error(
|
|
2245
|
+
`auto-rig: ${arm.role} needs a body layer (an arm hangs from the body's shoulder)`
|
|
2246
|
+
);
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2183
2249
|
function parseLayerRoles(fileNames) {
|
|
2184
2250
|
const seen = /* @__PURE__ */ new Map();
|
|
2185
2251
|
const out = [];
|
|
@@ -2205,11 +2271,12 @@ function parseLayerRoles(fileNames) {
|
|
|
2205
2271
|
`auto-rig: missing required role${missing.length > 1 ? "s" : ""} ${missing.join(", ")}`
|
|
2206
2272
|
);
|
|
2207
2273
|
}
|
|
2274
|
+
checkArmsHaveBody(seen.keys());
|
|
2208
2275
|
return out;
|
|
2209
2276
|
}
|
|
2210
2277
|
|
|
2211
2278
|
// src/auto-rig/generate.ts
|
|
2212
|
-
var
|
|
2279
|
+
var import_format8 = require("@ikijs/format");
|
|
2213
2280
|
|
|
2214
2281
|
// src/auto-rig/animations.ts
|
|
2215
2282
|
var import_format3 = require("@ikijs/format");
|
|
@@ -2360,6 +2427,12 @@ var DEFAULT_MOTIONS = {
|
|
|
2360
2427
|
]
|
|
2361
2428
|
};
|
|
2362
2429
|
|
|
2430
|
+
// src/auto-rig/arms.ts
|
|
2431
|
+
var import_format6 = require("@ikijs/format");
|
|
2432
|
+
|
|
2433
|
+
// src/auto-rig/body.ts
|
|
2434
|
+
var import_format5 = require("@ikijs/format");
|
|
2435
|
+
|
|
2363
2436
|
// src/auto-rig/layout.ts
|
|
2364
2437
|
function boxOfLayer(layer) {
|
|
2365
2438
|
return boxOfPixels(layer.bbox, layer.canvasW, layer.canvasH);
|
|
@@ -2433,6 +2506,651 @@ function meshPoints(mesh, box) {
|
|
|
2433
2506
|
return out;
|
|
2434
2507
|
}
|
|
2435
2508
|
|
|
2509
|
+
// src/auto-rig/drivers.ts
|
|
2510
|
+
var import_format4 = require("@ikijs/format");
|
|
2511
|
+
var SWAY_RANGE = 20;
|
|
2512
|
+
var SWAY_CURVE = 1.4;
|
|
2513
|
+
var HAIR_HANG = 0.35;
|
|
2514
|
+
var Z_STOPS = [-30, 0, 30];
|
|
2515
|
+
function localWarp(parameter, mesh, box, values, f) {
|
|
2516
|
+
const pts = meshPoints(mesh, box);
|
|
2517
|
+
const w = bw(box);
|
|
2518
|
+
const h = bh(box);
|
|
2519
|
+
return {
|
|
2520
|
+
parameter,
|
|
2521
|
+
keyforms: values.map((value) => ({
|
|
2522
|
+
value,
|
|
2523
|
+
offsets: pts.flatMap((p, i) => {
|
|
2524
|
+
const [dx, dy] = f(p, value, i);
|
|
2525
|
+
return [roundTo(dx / w, 1e-4), roundTo(dy / h, 1e-4)];
|
|
2526
|
+
})
|
|
2527
|
+
}))
|
|
2528
|
+
};
|
|
2529
|
+
}
|
|
2530
|
+
var rollOf = (v, deg = ROLL_DEG) => -v / 30 * deg * DEG;
|
|
2531
|
+
function motionReach(warps, mesh, box, extra = [0, 0]) {
|
|
2532
|
+
const out = {
|
|
2533
|
+
left: extra[0],
|
|
2534
|
+
right: extra[0],
|
|
2535
|
+
bottom: extra[1],
|
|
2536
|
+
top: extra[1]
|
|
2537
|
+
};
|
|
2538
|
+
if (mesh === void 0) return out;
|
|
2539
|
+
const pts = meshPoints(mesh, box);
|
|
2540
|
+
for (const warp of warps ?? []) {
|
|
2541
|
+
let l = 0;
|
|
2542
|
+
let r = 0;
|
|
2543
|
+
let b = 0;
|
|
2544
|
+
let t = 0;
|
|
2545
|
+
for (const k of warp.keyforms) {
|
|
2546
|
+
pts.forEach(([x, y], i) => {
|
|
2547
|
+
const px = x + k.offsets[2 * i] * bw(box);
|
|
2548
|
+
const py = y + k.offsets[2 * i + 1] * bh(box);
|
|
2549
|
+
l = Math.max(l, box.x0 - px);
|
|
2550
|
+
r = Math.max(r, px - box.x1);
|
|
2551
|
+
b = Math.max(b, box.y0 - py);
|
|
2552
|
+
t = Math.max(t, py - box.y1);
|
|
2553
|
+
});
|
|
2554
|
+
}
|
|
2555
|
+
out.left += l;
|
|
2556
|
+
out.right += r;
|
|
2557
|
+
out.bottom += b;
|
|
2558
|
+
out.top += t;
|
|
2559
|
+
}
|
|
2560
|
+
return out;
|
|
2561
|
+
}
|
|
2562
|
+
function blinkFold(param, mesh, box, travel, eye = box) {
|
|
2563
|
+
const yc = eye.y1 - travel * bh(eye);
|
|
2564
|
+
return localWarp(
|
|
2565
|
+
param,
|
|
2566
|
+
mesh,
|
|
2567
|
+
box,
|
|
2568
|
+
[0, 1],
|
|
2569
|
+
([, y], v) => v === 1 ? [0, 0] : [0, yc - y]
|
|
2570
|
+
);
|
|
2571
|
+
}
|
|
2572
|
+
function lashFold(param, mesh, lash, eye, travel) {
|
|
2573
|
+
const yc = eye.y1 - travel * bh(eye) - 0.04 * bh(eye);
|
|
2574
|
+
const c = cx(lash);
|
|
2575
|
+
const half = bw(lash) / 2;
|
|
2576
|
+
const sag = 0.3 * bh(lash);
|
|
2577
|
+
return localWarp(param, mesh, lash, [0, 1], ([x, y], v) => {
|
|
2578
|
+
if (v === 1) return [0, 0];
|
|
2579
|
+
const u = (x - c) / half;
|
|
2580
|
+
return [0, yc + (y - lash.y0) * 0.5 - sag * (1 - u * u) - y];
|
|
2581
|
+
});
|
|
2582
|
+
}
|
|
2583
|
+
function gazeBindings(irisW, share = 1) {
|
|
2584
|
+
const gx = AMPLITUDE.gazeX * irisW * share;
|
|
2585
|
+
const gy = AMPLITUDE.gazeY * irisW * share;
|
|
2586
|
+
return [
|
|
2587
|
+
{
|
|
2588
|
+
parameter: import_format4.StandardParameter.EyeballX,
|
|
2589
|
+
channel: "translateX",
|
|
2590
|
+
from: -round1(gx),
|
|
2591
|
+
to: round1(gx)
|
|
2592
|
+
},
|
|
2593
|
+
{
|
|
2594
|
+
parameter: import_format4.StandardParameter.EyeballY,
|
|
2595
|
+
channel: "translateY",
|
|
2596
|
+
from: -round1(gy),
|
|
2597
|
+
to: round1(gy)
|
|
2598
|
+
}
|
|
2599
|
+
];
|
|
2600
|
+
}
|
|
2601
|
+
function gazeReach(irisW, share = 1) {
|
|
2602
|
+
return [AMPLITUDE.gazeX * irisW * share, AMPLITUDE.gazeY * irisW * share];
|
|
2603
|
+
}
|
|
2604
|
+
function browReach(hh) {
|
|
2605
|
+
return AMPLITUDE.brow * hh;
|
|
2606
|
+
}
|
|
2607
|
+
function browBindings(side, reach) {
|
|
2608
|
+
const y = side === "L" ? import_format4.StandardParameter.BrowLeftY : import_format4.StandardParameter.BrowRightY;
|
|
2609
|
+
const a = side === "L" ? import_format4.StandardParameter.BrowLeftAngle : import_format4.StandardParameter.BrowRightAngle;
|
|
2610
|
+
return [
|
|
2611
|
+
{
|
|
2612
|
+
parameter: y,
|
|
2613
|
+
channel: "translateY",
|
|
2614
|
+
from: -round1(reach),
|
|
2615
|
+
to: round1(reach)
|
|
2616
|
+
},
|
|
2617
|
+
{ parameter: a, channel: "rotate", from: -BROW_TILT, to: BROW_TILT }
|
|
2618
|
+
];
|
|
2619
|
+
}
|
|
2620
|
+
var BROW_TILT = 15;
|
|
2621
|
+
function mouthForm(mesh, box) {
|
|
2622
|
+
const a = 0.3 * bh(box);
|
|
2623
|
+
const c = cx(box);
|
|
2624
|
+
const half = bw(box) / 2;
|
|
2625
|
+
return localWarp(import_format4.StandardParameter.MouthForm, mesh, box, [-1, 0, 1], ([x], v) => {
|
|
2626
|
+
if (v === 0) return [0, 0];
|
|
2627
|
+
const u = (x - c) / half;
|
|
2628
|
+
const widen = v > 0 ? 0.05 : 0.03;
|
|
2629
|
+
return [v * widen * (x - c), v * a * (u * u - 0.3)];
|
|
2630
|
+
});
|
|
2631
|
+
}
|
|
2632
|
+
function mouthOpenGrow(mesh, box) {
|
|
2633
|
+
const c = cx(box);
|
|
2634
|
+
return localWarp(
|
|
2635
|
+
import_format4.StandardParameter.MouthOpen,
|
|
2636
|
+
mesh,
|
|
2637
|
+
box,
|
|
2638
|
+
[0, 1],
|
|
2639
|
+
([x, y], v) => v === 1 ? [(AMPLITUDE.mouthOpenWidth - 1) * (x - c), 0] : [0, box.y1 + (y - box.y1) * 0.2 - y]
|
|
2640
|
+
);
|
|
2641
|
+
}
|
|
2642
|
+
function mouthWiden(mesh, box) {
|
|
2643
|
+
const c = cx(box);
|
|
2644
|
+
return localWarp(import_format4.StandardParameter.MouthOpen, mesh, box, [0, 1], ([x], v) => [
|
|
2645
|
+
v * (AMPLITUDE.mouthOpenWidth - 1) * (x - c),
|
|
2646
|
+
0
|
|
2647
|
+
]);
|
|
2648
|
+
}
|
|
2649
|
+
function hairSway(param, mesh, box, amp) {
|
|
2650
|
+
return localWarp(
|
|
2651
|
+
param,
|
|
2652
|
+
mesh,
|
|
2653
|
+
box,
|
|
2654
|
+
[-SWAY_RANGE, 0, SWAY_RANGE],
|
|
2655
|
+
([, y], v) => {
|
|
2656
|
+
const t = Math.max(0, (box.y1 - y) / bh(box));
|
|
2657
|
+
return [v / SWAY_RANGE * amp * t ** SWAY_CURVE, 0];
|
|
2658
|
+
}
|
|
2659
|
+
);
|
|
2660
|
+
}
|
|
2661
|
+
function tiltHang(mesh, box, pivot, weight, deg = ROLL_DEG) {
|
|
2662
|
+
return localWarp(import_format4.StandardParameter.AngleZ, mesh, box, Z_STOPS, ([x, y], v, i) => {
|
|
2663
|
+
const g = weight(y, i);
|
|
2664
|
+
if (g === 0 || v === 0) return [0, 0];
|
|
2665
|
+
const a = -g * rollOf(v, deg);
|
|
2666
|
+
const px = x - pivot[0];
|
|
2667
|
+
const py = y - pivot[1];
|
|
2668
|
+
return [
|
|
2669
|
+
pivot[0] + px * Math.cos(a) - py * Math.sin(a) - x,
|
|
2670
|
+
pivot[1] + px * Math.sin(a) + py * Math.cos(a) - y
|
|
2671
|
+
];
|
|
2672
|
+
});
|
|
2673
|
+
}
|
|
2674
|
+
function hairHangWeight(pivotY, bottom) {
|
|
2675
|
+
if (bottom >= pivotY) return () => 0;
|
|
2676
|
+
return (y) => HAIR_HANG * smoothstep(pivotY, bottom, y);
|
|
2677
|
+
}
|
|
2678
|
+
var round1 = (v) => roundTo(v, 0.1);
|
|
2679
|
+
|
|
2680
|
+
// src/auto-rig/grid.ts
|
|
2681
|
+
var X_STOPS = [-30, 0, 30];
|
|
2682
|
+
var Y_STOPS = [-30, 0, 30];
|
|
2683
|
+
function lattice(box, cell, min, max) {
|
|
2684
|
+
const cols = cellsFor(box.x1 - box.x0, cell, min, max);
|
|
2685
|
+
const rows = cellsFor(box.y1 - box.y0, cell, min, max);
|
|
2686
|
+
const points = [];
|
|
2687
|
+
for (let r = 0; r <= rows; r++) {
|
|
2688
|
+
const y = box.y1 - (box.y1 - box.y0) * r / rows;
|
|
2689
|
+
for (let c = 0; c <= cols; c++) {
|
|
2690
|
+
points.push(
|
|
2691
|
+
roundTo(box.x0 + (box.x1 - box.x0) * c / cols, 0.01),
|
|
2692
|
+
roundTo(y, 0.01)
|
|
2693
|
+
);
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
return { box, cols, rows, points };
|
|
2697
|
+
}
|
|
2698
|
+
function bake(l, field) {
|
|
2699
|
+
const out = [];
|
|
2700
|
+
for (const ay of Y_STOPS) {
|
|
2701
|
+
for (const ax of X_STOPS) {
|
|
2702
|
+
const offsets = [];
|
|
2703
|
+
for (let k = 0; k < l.points.length; k += 2) {
|
|
2704
|
+
if (ax === 0 && ay === 0) {
|
|
2705
|
+
offsets.push(0, 0);
|
|
2706
|
+
continue;
|
|
2707
|
+
}
|
|
2708
|
+
const [dx, dy] = field(l.points[k], l.points[k + 1], ax, ay);
|
|
2709
|
+
offsets.push(roundTo(dx, 0.1), roundTo(dy, 0.1));
|
|
2710
|
+
}
|
|
2711
|
+
out.push(offsets);
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
return out;
|
|
2715
|
+
}
|
|
2716
|
+
function keyformAt(keyforms, ax) {
|
|
2717
|
+
const j = Y_STOPS.indexOf(0);
|
|
2718
|
+
const row = X_STOPS.map((_, i) => keyforms[j * X_STOPS.length + i]);
|
|
2719
|
+
return blendStops(X_STOPS, row, ax);
|
|
2720
|
+
}
|
|
2721
|
+
function blendStops(values, offsets, v) {
|
|
2722
|
+
const x = clamp(v, values[0], values[values.length - 1]);
|
|
2723
|
+
let i = 0;
|
|
2724
|
+
while (i < values.length - 2 && x > values[i + 1]) i++;
|
|
2725
|
+
const t = (x - values[i]) / (values[i + 1] - values[i]);
|
|
2726
|
+
return offsets[i].map((o, k) => o + (offsets[i + 1][k] - o) * t);
|
|
2727
|
+
}
|
|
2728
|
+
function land(l, offsets, x, y) {
|
|
2729
|
+
const { cols, rows, points } = l;
|
|
2730
|
+
const stride = cols + 1;
|
|
2731
|
+
let col = cols - 1;
|
|
2732
|
+
for (let c = 0; c < cols; c++) {
|
|
2733
|
+
if (x < points[(c + 1) * 2]) {
|
|
2734
|
+
col = c;
|
|
2735
|
+
break;
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
let row = rows - 1;
|
|
2739
|
+
for (let r = 0; r < rows; r++) {
|
|
2740
|
+
if (y > points[(r + 1) * stride * 2 + 1]) {
|
|
2741
|
+
row = r;
|
|
2742
|
+
break;
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
const x0 = points[col * 2];
|
|
2746
|
+
const x1 = points[(col + 1) * 2];
|
|
2747
|
+
const yT = points[row * stride * 2 + 1];
|
|
2748
|
+
const yB = points[(row + 1) * stride * 2 + 1];
|
|
2749
|
+
const s = clamp((x - x0) / (x1 - x0), 0, 1);
|
|
2750
|
+
const t = clamp((yT - y) / (yT - yB), 0, 1);
|
|
2751
|
+
const at = (r, c) => {
|
|
2752
|
+
const k = (r * stride + c) * 2;
|
|
2753
|
+
return [points[k] + offsets[k], points[k + 1] + offsets[k + 1]];
|
|
2754
|
+
};
|
|
2755
|
+
const p00 = at(row, col);
|
|
2756
|
+
const p10 = at(row, col + 1);
|
|
2757
|
+
const p01 = at(row + 1, col);
|
|
2758
|
+
const p11 = at(row + 1, col + 1);
|
|
2759
|
+
const topX = p00[0] + (p10[0] - p00[0]) * s;
|
|
2760
|
+
const topY = p00[1] + (p10[1] - p00[1]) * s;
|
|
2761
|
+
const botX = p01[0] + (p11[0] - p01[0]) * s;
|
|
2762
|
+
const botY = p01[1] + (p11[1] - p01[1]) * s;
|
|
2763
|
+
return [topX + (botX - topX) * t, topY + (botY - topY) * t];
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2766
|
+
// src/auto-rig/body.ts
|
|
2767
|
+
var BODY_WARP_ID = "bodyWarp";
|
|
2768
|
+
var BODY_GRID_PAD = 6;
|
|
2769
|
+
var CHEST_DROP = 0.8;
|
|
2770
|
+
var HIP_BAND = [0.35, 0.75];
|
|
2771
|
+
var LEG_MIN = 0.1;
|
|
2772
|
+
var HIP_FRACTION = 0.5;
|
|
2773
|
+
var BODY_MESH_PX = 48;
|
|
2774
|
+
var BODY_STOPS = [-10, 0, 10];
|
|
2775
|
+
var FOLD_MARGIN = 0.01;
|
|
2776
|
+
function hipLine(layer) {
|
|
2777
|
+
const { bbox, canvasH, rowRuns } = layer;
|
|
2778
|
+
const top = canvasH / 2 - bbox.y;
|
|
2779
|
+
if (rowRuns !== void 0) {
|
|
2780
|
+
const legMin = LEG_MIN * bbox.w;
|
|
2781
|
+
const last = Math.floor(HIP_BAND[1] * bbox.h);
|
|
2782
|
+
for (let k = Math.ceil(HIP_BAND[0] * bbox.h); k <= last; k++) {
|
|
2783
|
+
const runs = rowRuns[k];
|
|
2784
|
+
let legs = 0;
|
|
2785
|
+
for (let i = 0; i < runs.length; i += 2) {
|
|
2786
|
+
if (runs[i + 1] - runs[i] >= legMin) legs++;
|
|
2787
|
+
}
|
|
2788
|
+
if (legs >= 2) return roundTo(top - k, 0.01);
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
if (bbox.y + bbox.h === canvasH) return roundTo(top - bbox.h, 0.01);
|
|
2792
|
+
return roundTo(top - HIP_FRACTION * bbox.h, 0.01);
|
|
2793
|
+
}
|
|
2794
|
+
function bodyLattice(body, chin, hh, pivots, anchor) {
|
|
2795
|
+
const b = boxOfLayer(body);
|
|
2796
|
+
const g = (v) => Math.round(roundTo(v, 0.01) * 100);
|
|
2797
|
+
const pad = BODY_GRID_PAD * 100;
|
|
2798
|
+
const all = [chin, ...pivots];
|
|
2799
|
+
const a = g(anchor);
|
|
2800
|
+
const p = Math.min(...all.map((q) => g(q.y)));
|
|
2801
|
+
if (p - a < 2) {
|
|
2802
|
+
throw new LayerGeometryError(
|
|
2803
|
+
`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`
|
|
2804
|
+
);
|
|
2805
|
+
}
|
|
2806
|
+
let full = Math.min(g(chin.y - CHEST_DROP * hh), p - pad);
|
|
2807
|
+
if (full - a < pad) {
|
|
2808
|
+
full = Math.min(Math.max(Math.round((a + p) / 2), a + 1), p - 1);
|
|
2809
|
+
}
|
|
2810
|
+
const top = Math.max(g(b.y1), ...all.map((q) => g(q.y))) + pad;
|
|
2811
|
+
const bottom = Math.min(a, g(b.y0)) - pad;
|
|
2812
|
+
const split = (from, to, cells) => {
|
|
2813
|
+
const n = Math.max(1, Math.min(cells, from - to));
|
|
2814
|
+
const out = [from];
|
|
2815
|
+
for (let i = 1; i < n; i++) {
|
|
2816
|
+
out.push(Math.round(from - (from - to) * i / n));
|
|
2817
|
+
}
|
|
2818
|
+
out.push(to);
|
|
2819
|
+
return out;
|
|
2820
|
+
};
|
|
2821
|
+
const bandA = split(top, full, cellsFor((top - full) / 100, 96, 1, 8));
|
|
2822
|
+
const ramp = split(full, a, cellsFor((full - a) / 100, 96, 2, 8));
|
|
2823
|
+
const rows = [...bandA, ...ramp.slice(1, -1)];
|
|
2824
|
+
const ys = [...rows.map((y) => y / 100), anchor, bottom / 100];
|
|
2825
|
+
const weights = [...rows.map((y) => smoothstep(a, full, y)), 0, 0];
|
|
2826
|
+
const x0 = Math.min(b.x0, ...all.map((q) => q.x)) - BODY_GRID_PAD;
|
|
2827
|
+
const x1 = Math.max(b.x1, ...all.map((q) => q.x)) + BODY_GRID_PAD;
|
|
2828
|
+
const cols = cellsFor(x1 - x0, 96, 2, 8);
|
|
2829
|
+
const xs = [];
|
|
2830
|
+
for (let c = 0; c <= cols; c++) {
|
|
2831
|
+
xs.push(roundTo(x0 + (x1 - x0) * c / cols, 0.01));
|
|
2832
|
+
}
|
|
2833
|
+
return { xs, ys, weights, yFull: full / 100 };
|
|
2834
|
+
}
|
|
2835
|
+
function bodyMesh(b, rows) {
|
|
2836
|
+
const lines = [b.y1, ...rows.filter((y) => y > b.y0 && y < b.y1), b.y0];
|
|
2837
|
+
const ys = [lines[0]];
|
|
2838
|
+
for (let i = 1; i < lines.length; i++) {
|
|
2839
|
+
const [from, to] = [lines[i - 1], lines[i]];
|
|
2840
|
+
const n = Math.ceil((from - to) / BODY_MESH_PX);
|
|
2841
|
+
for (let k = 1; k < n; k++) ys.push(from - (from - to) * k / n);
|
|
2842
|
+
ys.push(to);
|
|
2843
|
+
}
|
|
2844
|
+
const cols = cellsFor(bw(b), BODY_MESH_PX, 2, 16);
|
|
2845
|
+
const vertices = [];
|
|
2846
|
+
const uvs = [];
|
|
2847
|
+
for (const y of ys) {
|
|
2848
|
+
const vy = roundTo((y - cy(b)) / bh(b), 1e-5);
|
|
2849
|
+
for (let c = 0; c <= cols; c++) {
|
|
2850
|
+
const vx = roundTo(c / cols - 0.5, 1e-5);
|
|
2851
|
+
vertices.push(vx, vy);
|
|
2852
|
+
uvs.push(roundTo(vx + 0.5, 1e-5), roundTo(0.5 - vy, 1e-5));
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
const indices = [];
|
|
2856
|
+
for (let r = 0; r + 1 < ys.length; r++) {
|
|
2857
|
+
for (let c = 0; c < cols; c++) {
|
|
2858
|
+
const tl = r * (cols + 1) + c;
|
|
2859
|
+
const tr = tl + 1;
|
|
2860
|
+
const bl = tl + cols + 1;
|
|
2861
|
+
const br = bl + 1;
|
|
2862
|
+
indices.push(bl, br, tl, tl, br, tr);
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
return { vertices, uvs, indices };
|
|
2866
|
+
}
|
|
2867
|
+
function buildBodyWarp({
|
|
2868
|
+
body,
|
|
2869
|
+
chin,
|
|
2870
|
+
hh,
|
|
2871
|
+
pivots
|
|
2872
|
+
}) {
|
|
2873
|
+
const b = boxOfLayer(body);
|
|
2874
|
+
const anchor = hipLine(body);
|
|
2875
|
+
const { xs, ys, weights } = bodyLattice(body, chin, hh, pivots, anchor);
|
|
2876
|
+
const hx = cx(b);
|
|
2877
|
+
const turn = (x, t) => t * BODY.slide * hh - Math.abs(t) * BODY.narrow * (x - hx);
|
|
2878
|
+
const roll = (x, y, deg) => {
|
|
2879
|
+
const c = Math.cos(deg * DEG) - 1;
|
|
2880
|
+
const s = Math.sin(deg * DEG);
|
|
2881
|
+
const [dx, dy] = [x - hx, y - anchor];
|
|
2882
|
+
return [c * dx - s * dy, s * dx + c * dy];
|
|
2883
|
+
};
|
|
2884
|
+
const warp = (parameter, values, field) => {
|
|
2885
|
+
const extreme = Math.max(...values.map(Math.abs));
|
|
2886
|
+
return {
|
|
2887
|
+
parameter,
|
|
2888
|
+
keyforms: values.map((value) => ({
|
|
2889
|
+
value,
|
|
2890
|
+
offsets: ys.flatMap(
|
|
2891
|
+
(y, r) => xs.flatMap((x) => {
|
|
2892
|
+
const [dx, dy] = field(x, y, value / extreme);
|
|
2893
|
+
return [
|
|
2894
|
+
roundTo(weights[r] * dx, 0.01),
|
|
2895
|
+
roundTo(weights[r] * dy, 0.01)
|
|
2896
|
+
];
|
|
2897
|
+
})
|
|
2898
|
+
)
|
|
2899
|
+
}))
|
|
2900
|
+
};
|
|
2901
|
+
};
|
|
2902
|
+
const deformer = {
|
|
2903
|
+
kind: "warp",
|
|
2904
|
+
id: BODY_WARP_ID,
|
|
2905
|
+
grid: {
|
|
2906
|
+
cols: xs.length - 1,
|
|
2907
|
+
rows: ys.length - 1,
|
|
2908
|
+
points: ys.flatMap((y) => xs.flatMap((x) => [x, y]))
|
|
2909
|
+
},
|
|
2910
|
+
warps: [
|
|
2911
|
+
warp(import_format5.StandardParameter.BodyAngleX, BODY_STOPS, (x, _y, t) => [turn(x, t), 0]),
|
|
2912
|
+
warp(import_format5.StandardParameter.BodyAngleY, BODY_STOPS, (_x, _y, t) => [0, t * BODY.bow * hh]),
|
|
2913
|
+
warp(
|
|
2914
|
+
import_format5.StandardParameter.BodyAngleZ,
|
|
2915
|
+
BODY_STOPS,
|
|
2916
|
+
(x, y, t) => roll(x, y, -t * BODY.rollDeg)
|
|
2917
|
+
),
|
|
2918
|
+
warp(import_format5.StandardParameter.AngleX, X_STOPS, (x, _y, t) => [BODY.followX * turn(x, t), 0]),
|
|
2919
|
+
warp(import_format5.StandardParameter.AngleZ, Z_STOPS, (x, y, t) => roll(x, y, -t * BODY.followRoll)),
|
|
2920
|
+
warp(import_format5.StandardParameter.Breath, [0, 1], (_x, _y, t) => [0, t * AMPLITUDE.breathBody * hh])
|
|
2921
|
+
]
|
|
2922
|
+
};
|
|
2923
|
+
refuseFold(body, deformer, anchor, [chin, ...pivots]);
|
|
2924
|
+
return { deformer, mesh: bodyMesh(b, ys) };
|
|
2925
|
+
}
|
|
2926
|
+
function refuseFold(body, d, anchor, pivots) {
|
|
2927
|
+
const { cols, rows, points } = d.grid;
|
|
2928
|
+
const warps = d.warps ?? [];
|
|
2929
|
+
const stride = cols + 1;
|
|
2930
|
+
for (let r = 0; r < rows; r++) {
|
|
2931
|
+
for (let c = 0; c < cols; c++) {
|
|
2932
|
+
const tl = r * stride + c;
|
|
2933
|
+
const [tr, bl, br] = [tl + 1, tl + stride, tl + stride + 1];
|
|
2934
|
+
const corners = [
|
|
2935
|
+
[bl, br, tl],
|
|
2936
|
+
[br, tr, bl],
|
|
2937
|
+
[tr, tl, br],
|
|
2938
|
+
[tl, bl, tr]
|
|
2939
|
+
];
|
|
2940
|
+
for (const [p, q, s] of corners) {
|
|
2941
|
+
const fold = leastArea(points, warps, p, q, s);
|
|
2942
|
+
if (fold.least >= FOLD_MARGIN * fold.rest) continue;
|
|
2943
|
+
const lowest = roundTo(Math.min(...pivots.map((v) => v.y)), 0.01);
|
|
2944
|
+
const pose = warps.map((w, i) => `${w.parameter} ${w.keyforms[fold.stops[i]].value}`).join(", ");
|
|
2945
|
+
throw new LayerGeometryError(
|
|
2946
|
+
`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}`
|
|
2947
|
+
);
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
function leastArea(points, warps, p, q, s) {
|
|
2953
|
+
const moves = warps.map(
|
|
2954
|
+
(w) => w.keyforms.map(({ offsets: o }) => [
|
|
2955
|
+
o[q * 2] - o[p * 2],
|
|
2956
|
+
o[q * 2 + 1] - o[p * 2 + 1],
|
|
2957
|
+
o[s * 2] - o[p * 2],
|
|
2958
|
+
o[s * 2 + 1] - o[p * 2 + 1]
|
|
2959
|
+
])
|
|
2960
|
+
);
|
|
2961
|
+
const cross = (e) => e[0] * e[3] - e[1] * e[2];
|
|
2962
|
+
const edges = [
|
|
2963
|
+
points[q * 2] - points[p * 2],
|
|
2964
|
+
points[q * 2 + 1] - points[p * 2 + 1],
|
|
2965
|
+
points[s * 2] - points[p * 2],
|
|
2966
|
+
points[s * 2 + 1] - points[p * 2 + 1]
|
|
2967
|
+
];
|
|
2968
|
+
let least = Infinity;
|
|
2969
|
+
let stops = [];
|
|
2970
|
+
const at = [];
|
|
2971
|
+
const walk = (w, ax, ay, bx, by) => {
|
|
2972
|
+
if (w === moves.length) {
|
|
2973
|
+
const area = ax * by - ay * bx;
|
|
2974
|
+
if (area < least) [least, stops] = [area, [...at]];
|
|
2975
|
+
return;
|
|
2976
|
+
}
|
|
2977
|
+
moves[w].forEach(([mx, my, nx, ny], k) => {
|
|
2978
|
+
at[w] = k;
|
|
2979
|
+
walk(w + 1, ax + mx, ay + my, bx + nx, by + ny);
|
|
2980
|
+
});
|
|
2981
|
+
};
|
|
2982
|
+
walk(0, edges[0], edges[1], edges[2], edges[3]);
|
|
2983
|
+
for (const m of moves) {
|
|
2984
|
+
let most = 0;
|
|
2985
|
+
for (let k = 1; k < m.length; k++) {
|
|
2986
|
+
most = Math.max(most, cross(m[k].map((v, i) => v - m[k - 1][i])));
|
|
2987
|
+
}
|
|
2988
|
+
least -= most / 4;
|
|
2989
|
+
}
|
|
2990
|
+
return { rest: cross(edges), least, stops };
|
|
2991
|
+
}
|
|
2992
|
+
function headOwnRoll(hasBody) {
|
|
2993
|
+
return hasBody ? ROLL_DEG - BODY.followRoll : ROLL_DEG;
|
|
2994
|
+
}
|
|
2995
|
+
function headOwnBreath(hasBody) {
|
|
2996
|
+
return hasBody ? AMPLITUDE.breathHead - AMPLITUDE.breathBody : AMPLITUDE.breathHead;
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
// src/auto-rig/arms.ts
|
|
3000
|
+
var ELBOW_AT = 0.42;
|
|
3001
|
+
var CAP_ROWS = [0.1, 0.3];
|
|
3002
|
+
var CAP_SEGMENTS = 12;
|
|
3003
|
+
var ARM_RANGE = [-30, 150];
|
|
3004
|
+
var ARM_PARAMS = {
|
|
3005
|
+
arm_L: { arm: import_format6.StandardParameter.ArmLeft, elbow: import_format6.StandardParameter.ElbowLeft },
|
|
3006
|
+
arm_R: { arm: import_format6.StandardParameter.ArmRight, elbow: import_format6.StandardParameter.ElbowRight }
|
|
3007
|
+
};
|
|
3008
|
+
var ARM_IDS = {
|
|
3009
|
+
arm_L: {
|
|
3010
|
+
forearm: "forearm_L",
|
|
3011
|
+
armDeformer: "armDeformer_L",
|
|
3012
|
+
forearmDeformer: "forearmDeformer_L"
|
|
3013
|
+
},
|
|
3014
|
+
arm_R: {
|
|
3015
|
+
forearm: "forearm_R",
|
|
3016
|
+
armDeformer: "armDeformer_R",
|
|
3017
|
+
forearmDeformer: "forearmDeformer_R"
|
|
3018
|
+
}
|
|
3019
|
+
};
|
|
3020
|
+
function armGeometry(layer, bodyAxisX) {
|
|
3021
|
+
const { bbox, canvasW, canvasH, rowRuns } = layer;
|
|
3022
|
+
const b = boxOfLayer(layer);
|
|
3023
|
+
const span = (k) => {
|
|
3024
|
+
const runs = rowRuns?.[k] ?? [];
|
|
3025
|
+
if (runs.length === 0) return [bbox.x, bbox.x + bbox.w];
|
|
3026
|
+
let [s2, e2] = [runs[0], runs[1]];
|
|
3027
|
+
for (let i = 2; i < runs.length; i += 2) {
|
|
3028
|
+
if (runs[i + 1] - runs[i] > e2 - s2) [s2, e2] = [runs[i], runs[i + 1]];
|
|
3029
|
+
}
|
|
3030
|
+
return [s2, e2];
|
|
3031
|
+
};
|
|
3032
|
+
const centreX = (k) => {
|
|
3033
|
+
const [s2, e2] = span(k);
|
|
3034
|
+
return (s2 + e2) / 2 - canvasW / 2;
|
|
3035
|
+
};
|
|
3036
|
+
const rowY = (k) => canvasH / 2 - (bbox.y + k + 0.5);
|
|
3037
|
+
const painted = rowRuns?.flatMap((runs, k) => runs.length > 0 ? [k] : []) ?? [];
|
|
3038
|
+
const first = painted[0] ?? 0;
|
|
3039
|
+
const last = painted.at(-1) ?? bbox.h - 1;
|
|
3040
|
+
const lo = Math.ceil(CAP_ROWS[0] * bbox.h);
|
|
3041
|
+
const hi = Math.max(lo, Math.floor(CAP_ROWS[1] * bbox.h));
|
|
3042
|
+
const widths = [];
|
|
3043
|
+
for (let k = lo; k <= hi; k++) {
|
|
3044
|
+
const [s2, e2] = span(k);
|
|
3045
|
+
widths.push(e2 - s2);
|
|
3046
|
+
}
|
|
3047
|
+
widths.sort((p, q) => p - q);
|
|
3048
|
+
const ru = widths[widths.length >> 1] / 2;
|
|
3049
|
+
const ks = first + ru;
|
|
3050
|
+
const shoulder = {
|
|
3051
|
+
x: roundTo(centreX(Math.round(ks)), 0.01),
|
|
3052
|
+
y: roundTo(rowY(ks), 0.01)
|
|
3053
|
+
};
|
|
3054
|
+
const ke = Math.round(ks + ELBOW_AT * (last - ks));
|
|
3055
|
+
if (ke <= ks) {
|
|
3056
|
+
throw new LayerGeometryError(
|
|
3057
|
+
`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)`
|
|
3058
|
+
);
|
|
3059
|
+
}
|
|
3060
|
+
const elbow = { x: roundTo(centreX(ke), 0.01), y: roundTo(rowY(ke), 0.01) };
|
|
3061
|
+
const [s, e] = span(ke);
|
|
3062
|
+
const capRadius = Math.min(
|
|
3063
|
+
(e - s) / 2 + 1,
|
|
3064
|
+
elbow.x - b.x0,
|
|
3065
|
+
b.x1 - elbow.x,
|
|
3066
|
+
b.y1 - elbow.y
|
|
3067
|
+
);
|
|
3068
|
+
return {
|
|
3069
|
+
shoulder,
|
|
3070
|
+
shoulderRadius: ru,
|
|
3071
|
+
elbow,
|
|
3072
|
+
capRadius,
|
|
3073
|
+
side: shoulder.x > bodyAxisX ? 1 : -1
|
|
3074
|
+
};
|
|
3075
|
+
}
|
|
3076
|
+
function meshOf(vertices, indices) {
|
|
3077
|
+
const uvs = vertices.map(
|
|
3078
|
+
(v, i) => roundTo(i % 2 === 0 ? v + 0.5 : 0.5 - v, 1e-5)
|
|
3079
|
+
);
|
|
3080
|
+
return { vertices, uvs, indices };
|
|
3081
|
+
}
|
|
3082
|
+
function armParts(role, layer, g, order) {
|
|
3083
|
+
const b = boxOfLayer(layer);
|
|
3084
|
+
const lx = (x) => roundTo((x - cx(b)) / bw(b), 1e-5);
|
|
3085
|
+
const ly = (y) => roundTo((y - cy(b)) / bh(b), 1e-5);
|
|
3086
|
+
const seam = ly(g.elbow.y);
|
|
3087
|
+
const band = (top, bottom) => [
|
|
3088
|
+
-0.5,
|
|
3089
|
+
top,
|
|
3090
|
+
0.5,
|
|
3091
|
+
top,
|
|
3092
|
+
-0.5,
|
|
3093
|
+
bottom,
|
|
3094
|
+
0.5,
|
|
3095
|
+
bottom
|
|
3096
|
+
];
|
|
3097
|
+
const quad = [2, 3, 0, 0, 3, 1];
|
|
3098
|
+
const cap = [lx(g.elbow.x), seam];
|
|
3099
|
+
const fan = [];
|
|
3100
|
+
for (let i = 0; i <= CAP_SEGMENTS; i++) {
|
|
3101
|
+
const a = Math.PI * i / CAP_SEGMENTS;
|
|
3102
|
+
cap.push(
|
|
3103
|
+
lx(g.elbow.x + g.capRadius * Math.cos(a)),
|
|
3104
|
+
ly(g.elbow.y + g.capRadius * Math.sin(a))
|
|
3105
|
+
);
|
|
3106
|
+
if (i > 0) fan.push(4, 4 + i, 5 + i);
|
|
3107
|
+
}
|
|
3108
|
+
const part = (id, deformer, at, mesh) => ({
|
|
3109
|
+
id,
|
|
3110
|
+
color: [1, 1, 1, 1],
|
|
3111
|
+
width: bw(b),
|
|
3112
|
+
height: bh(b),
|
|
3113
|
+
transform: { x: cx(b), y: cy(b) },
|
|
3114
|
+
order: at,
|
|
3115
|
+
deformer,
|
|
3116
|
+
mesh
|
|
3117
|
+
});
|
|
3118
|
+
const ids = ARM_IDS[role];
|
|
3119
|
+
return [
|
|
3120
|
+
part(role, ids.armDeformer, order, meshOf(band(0.5, seam), quad)),
|
|
3121
|
+
part(
|
|
3122
|
+
ids.forearm,
|
|
3123
|
+
ids.forearmDeformer,
|
|
3124
|
+
order + 1,
|
|
3125
|
+
meshOf([...band(seam, -0.5), ...cap], [...quad, ...fan])
|
|
3126
|
+
)
|
|
3127
|
+
];
|
|
3128
|
+
}
|
|
3129
|
+
function armDeformers(role, g) {
|
|
3130
|
+
const ids = ARM_IDS[role];
|
|
3131
|
+
const params = ARM_PARAMS[role];
|
|
3132
|
+
const rotate = (parameter) => ({
|
|
3133
|
+
parameter,
|
|
3134
|
+
channel: "rotate",
|
|
3135
|
+
from: g.side * ARM_RANGE[0],
|
|
3136
|
+
to: g.side * ARM_RANGE[1]
|
|
3137
|
+
});
|
|
3138
|
+
return [
|
|
3139
|
+
{
|
|
3140
|
+
id: ids.armDeformer,
|
|
3141
|
+
parent: BODY_WARP_ID,
|
|
3142
|
+
pivot: { ...g.shoulder },
|
|
3143
|
+
bindings: [rotate(params.arm)]
|
|
3144
|
+
},
|
|
3145
|
+
{
|
|
3146
|
+
id: ids.forearmDeformer,
|
|
3147
|
+
parent: ids.armDeformer,
|
|
3148
|
+
pivot: { ...g.elbow },
|
|
3149
|
+
bindings: [rotate(params.elbow)]
|
|
3150
|
+
}
|
|
3151
|
+
];
|
|
3152
|
+
}
|
|
3153
|
+
|
|
2436
3154
|
// src/auto-rig/checks.ts
|
|
2437
3155
|
var finite = (v) => typeof v === "number" && Number.isFinite(v);
|
|
2438
3156
|
var isObject = (v) => typeof v === "object" && v !== null;
|
|
@@ -2536,6 +3254,7 @@ function checkLayers(layers, canvas) {
|
|
|
2536
3254
|
if (missing.length > 0) {
|
|
2537
3255
|
throw new Error(`auto-rig: missing required role(s) ${missing.join(", ")}`);
|
|
2538
3256
|
}
|
|
3257
|
+
checkArmsHaveBody(seen);
|
|
2539
3258
|
}
|
|
2540
3259
|
function checkHeadEdges(edges, byRole) {
|
|
2541
3260
|
if (edges === void 0) return;
|
|
@@ -2616,131 +3335,45 @@ function checkStrandEdges(strand, irises, hair) {
|
|
|
2616
3335
|
for (const [k, x] of [
|
|
2617
3336
|
["irisOuter", outer],
|
|
2618
3337
|
["irisInner", inner]
|
|
2619
|
-
]) {
|
|
2620
|
-
if (x < iris.x0 || x > iris.x1) {
|
|
2621
|
-
throw new TurnTargetError(`${at}.${k} ${x} is off its iris's crop`);
|
|
2622
|
-
}
|
|
2623
|
-
}
|
|
2624
|
-
if (sign * (outer - inner) <= 0) {
|
|
2625
|
-
throw new TurnTargetError(
|
|
2626
|
-
`${at}.irisOuter ${outer} is not strictly outward of irisInner ${inner}`
|
|
2627
|
-
);
|
|
2628
|
-
}
|
|
2629
|
-
for (const [k, x] of [
|
|
2630
|
-
["runOuter", runOuter],
|
|
2631
|
-
["runFace", runFace]
|
|
2632
|
-
]) {
|
|
2633
|
-
if (x !== null && (x < hair.x0 || x > hair.x1)) {
|
|
2634
|
-
throw new TurnTargetError(`${at}.${k} ${x} is off hair_front's crop`);
|
|
2635
|
-
}
|
|
2636
|
-
}
|
|
2637
|
-
if (sign * (runOuter - cx(iris)) <= 0) {
|
|
2638
|
-
throw new TurnTargetError(
|
|
2639
|
-
`${at}.runOuter ${runOuter} is not strictly outward of its iris's centre`
|
|
2640
|
-
);
|
|
2641
|
-
}
|
|
2642
|
-
if (runFace !== null && sign * (runOuter - runFace) <= 0) {
|
|
2643
|
-
throw new TurnTargetError(
|
|
2644
|
-
`${at}.runOuter ${runOuter} is not strictly outward of runFace ${runFace}`
|
|
2645
|
-
);
|
|
2646
|
-
}
|
|
2647
|
-
if (runFace !== null && sign * (runFace - cx(other)) <= 0) {
|
|
2648
|
-
throw new TurnTargetError(
|
|
2649
|
-
`${at}.runFace ${runFace} reaches the other iris's centre; a run that does is a fringe, runFace null`
|
|
2650
|
-
);
|
|
2651
|
-
}
|
|
2652
|
-
}
|
|
2653
|
-
}
|
|
2654
|
-
|
|
2655
|
-
// src/auto-rig/context.ts
|
|
2656
|
-
var import_format4 = require("@ikijs/format");
|
|
2657
|
-
|
|
2658
|
-
// src/auto-rig/grid.ts
|
|
2659
|
-
var X_STOPS = [-30, 0, 30];
|
|
2660
|
-
var Y_STOPS = [-30, 0, 30];
|
|
2661
|
-
function lattice(box, cell, min, max) {
|
|
2662
|
-
const cols = cellsFor(box.x1 - box.x0, cell, min, max);
|
|
2663
|
-
const rows = cellsFor(box.y1 - box.y0, cell, min, max);
|
|
2664
|
-
const points = [];
|
|
2665
|
-
for (let r = 0; r <= rows; r++) {
|
|
2666
|
-
const y = box.y1 - (box.y1 - box.y0) * r / rows;
|
|
2667
|
-
for (let c = 0; c <= cols; c++) {
|
|
2668
|
-
points.push(
|
|
2669
|
-
roundTo(box.x0 + (box.x1 - box.x0) * c / cols, 0.01),
|
|
2670
|
-
roundTo(y, 0.01)
|
|
2671
|
-
);
|
|
2672
|
-
}
|
|
2673
|
-
}
|
|
2674
|
-
return { box, cols, rows, points };
|
|
2675
|
-
}
|
|
2676
|
-
function bake(l, field) {
|
|
2677
|
-
const out = [];
|
|
2678
|
-
for (const ay of Y_STOPS) {
|
|
2679
|
-
for (const ax of X_STOPS) {
|
|
2680
|
-
const offsets = [];
|
|
2681
|
-
for (let k = 0; k < l.points.length; k += 2) {
|
|
2682
|
-
if (ax === 0 && ay === 0) {
|
|
2683
|
-
offsets.push(0, 0);
|
|
2684
|
-
continue;
|
|
2685
|
-
}
|
|
2686
|
-
const [dx, dy] = field(l.points[k], l.points[k + 1], ax, ay);
|
|
2687
|
-
offsets.push(roundTo(dx, 0.1), roundTo(dy, 0.1));
|
|
3338
|
+
]) {
|
|
3339
|
+
if (x < iris.x0 || x > iris.x1) {
|
|
3340
|
+
throw new TurnTargetError(`${at}.${k} ${x} is off its iris's crop`);
|
|
2688
3341
|
}
|
|
2689
|
-
out.push(offsets);
|
|
2690
3342
|
}
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
}
|
|
2694
|
-
|
|
2695
|
-
const j = Y_STOPS.indexOf(0);
|
|
2696
|
-
const row = X_STOPS.map((_, i) => keyforms[j * X_STOPS.length + i]);
|
|
2697
|
-
return blendStops(X_STOPS, row, ax);
|
|
2698
|
-
}
|
|
2699
|
-
function blendStops(values, offsets, v) {
|
|
2700
|
-
const x = clamp(v, values[0], values[values.length - 1]);
|
|
2701
|
-
let i = 0;
|
|
2702
|
-
while (i < values.length - 2 && x > values[i + 1]) i++;
|
|
2703
|
-
const t = (x - values[i]) / (values[i + 1] - values[i]);
|
|
2704
|
-
return offsets[i].map((o, k) => o + (offsets[i + 1][k] - o) * t);
|
|
2705
|
-
}
|
|
2706
|
-
function land(l, offsets, x, y) {
|
|
2707
|
-
const { cols, rows, points } = l;
|
|
2708
|
-
const stride = cols + 1;
|
|
2709
|
-
let col = cols - 1;
|
|
2710
|
-
for (let c = 0; c < cols; c++) {
|
|
2711
|
-
if (x < points[(c + 1) * 2]) {
|
|
2712
|
-
col = c;
|
|
2713
|
-
break;
|
|
3343
|
+
if (sign * (outer - inner) <= 0) {
|
|
3344
|
+
throw new TurnTargetError(
|
|
3345
|
+
`${at}.irisOuter ${outer} is not strictly outward of irisInner ${inner}`
|
|
3346
|
+
);
|
|
2714
3347
|
}
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
3348
|
+
for (const [k, x] of [
|
|
3349
|
+
["runOuter", runOuter],
|
|
3350
|
+
["runFace", runFace]
|
|
3351
|
+
]) {
|
|
3352
|
+
if (x !== null && (x < hair.x0 || x > hair.x1)) {
|
|
3353
|
+
throw new TurnTargetError(`${at}.${k} ${x} is off hair_front's crop`);
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
if (sign * (runOuter - cx(iris)) <= 0) {
|
|
3357
|
+
throw new TurnTargetError(
|
|
3358
|
+
`${at}.runOuter ${runOuter} is not strictly outward of its iris's centre`
|
|
3359
|
+
);
|
|
3360
|
+
}
|
|
3361
|
+
if (runFace !== null && sign * (runOuter - runFace) <= 0) {
|
|
3362
|
+
throw new TurnTargetError(
|
|
3363
|
+
`${at}.runOuter ${runOuter} is not strictly outward of runFace ${runFace}`
|
|
3364
|
+
);
|
|
3365
|
+
}
|
|
3366
|
+
if (runFace !== null && sign * (runFace - cx(other)) <= 0) {
|
|
3367
|
+
throw new TurnTargetError(
|
|
3368
|
+
`${at}.runFace ${runFace} reaches the other iris's centre; a run that does is a fringe, runFace null`
|
|
3369
|
+
);
|
|
2721
3370
|
}
|
|
2722
3371
|
}
|
|
2723
|
-
const x0 = points[col * 2];
|
|
2724
|
-
const x1 = points[(col + 1) * 2];
|
|
2725
|
-
const yT = points[row * stride * 2 + 1];
|
|
2726
|
-
const yB = points[(row + 1) * stride * 2 + 1];
|
|
2727
|
-
const s = clamp((x - x0) / (x1 - x0), 0, 1);
|
|
2728
|
-
const t = clamp((yT - y) / (yT - yB), 0, 1);
|
|
2729
|
-
const at = (r, c) => {
|
|
2730
|
-
const k = (r * stride + c) * 2;
|
|
2731
|
-
return [points[k] + offsets[k], points[k + 1] + offsets[k + 1]];
|
|
2732
|
-
};
|
|
2733
|
-
const p00 = at(row, col);
|
|
2734
|
-
const p10 = at(row, col + 1);
|
|
2735
|
-
const p01 = at(row + 1, col);
|
|
2736
|
-
const p11 = at(row + 1, col + 1);
|
|
2737
|
-
const topX = p00[0] + (p10[0] - p00[0]) * s;
|
|
2738
|
-
const topY = p00[1] + (p10[1] - p00[1]) * s;
|
|
2739
|
-
const botX = p01[0] + (p11[0] - p01[0]) * s;
|
|
2740
|
-
const botY = p01[1] + (p11[1] - p01[1]) * s;
|
|
2741
|
-
return [topX + (botX - topX) * t, topY + (botY - topY) * t];
|
|
2742
3372
|
}
|
|
2743
3373
|
|
|
3374
|
+
// src/auto-rig/context.ts
|
|
3375
|
+
var import_format7 = require("@ikijs/format");
|
|
3376
|
+
|
|
2744
3377
|
// src/auto-rig/head.ts
|
|
2745
3378
|
function outerEdge(runs, axisX, side) {
|
|
2746
3379
|
if (runs.length === 0) return void 0;
|
|
@@ -3770,6 +4403,7 @@ function familyField(m, family) {
|
|
|
3770
4403
|
case "hair_back":
|
|
3771
4404
|
return hairField(m, m.hairBack, NOD.hairBack);
|
|
3772
4405
|
case "body":
|
|
4406
|
+
case "arm":
|
|
3773
4407
|
return () => [0, 0];
|
|
3774
4408
|
}
|
|
3775
4409
|
}
|
|
@@ -3869,7 +4503,7 @@ function renderedLander(turn, parts, grids, from = /* @__PURE__ */ new Map()) {
|
|
|
3869
4503
|
const family = roleSpec(role).family;
|
|
3870
4504
|
const part = parts.find((p) => p.id === role);
|
|
3871
4505
|
const g = grids.get(family);
|
|
3872
|
-
const warp = part?.warps?.find((w) => w.parameter ===
|
|
4506
|
+
const warp = part?.warps?.find((w) => w.parameter === import_format7.StandardParameter.AngleX);
|
|
3873
4507
|
if (part === void 0 || part.mesh === void 0 || !g && !warp) {
|
|
3874
4508
|
return x + field(role)(x, y, ax, 0)[0];
|
|
3875
4509
|
}
|
|
@@ -4578,177 +5212,6 @@ function buildTurn(frame, landmarks, q) {
|
|
|
4578
5212
|
};
|
|
4579
5213
|
}
|
|
4580
5214
|
|
|
4581
|
-
// src/auto-rig/drivers.ts
|
|
4582
|
-
var import_format5 = require("@ikijs/format");
|
|
4583
|
-
var SWAY_RANGE = 20;
|
|
4584
|
-
var SWAY_CURVE = 1.4;
|
|
4585
|
-
var HAIR_HANG = 0.35;
|
|
4586
|
-
var Z_STOPS = [-30, 0, 30];
|
|
4587
|
-
function localWarp(parameter, mesh, box, values, f) {
|
|
4588
|
-
const pts = meshPoints(mesh, box);
|
|
4589
|
-
const w = bw(box);
|
|
4590
|
-
const h = bh(box);
|
|
4591
|
-
return {
|
|
4592
|
-
parameter,
|
|
4593
|
-
keyforms: values.map((value) => ({
|
|
4594
|
-
value,
|
|
4595
|
-
offsets: pts.flatMap((p, i) => {
|
|
4596
|
-
const [dx, dy] = f(p, value, i);
|
|
4597
|
-
return [roundTo(dx / w, 1e-4), roundTo(dy / h, 1e-4)];
|
|
4598
|
-
})
|
|
4599
|
-
}))
|
|
4600
|
-
};
|
|
4601
|
-
}
|
|
4602
|
-
var rollOf = (v) => -v / 30 * ROLL_DEG * DEG;
|
|
4603
|
-
function motionReach(warps, mesh, box, extra = [0, 0]) {
|
|
4604
|
-
const out = {
|
|
4605
|
-
left: extra[0],
|
|
4606
|
-
right: extra[0],
|
|
4607
|
-
bottom: extra[1],
|
|
4608
|
-
top: extra[1]
|
|
4609
|
-
};
|
|
4610
|
-
if (mesh === void 0) return out;
|
|
4611
|
-
const pts = meshPoints(mesh, box);
|
|
4612
|
-
for (const warp of warps ?? []) {
|
|
4613
|
-
let l = 0;
|
|
4614
|
-
let r = 0;
|
|
4615
|
-
let b = 0;
|
|
4616
|
-
let t = 0;
|
|
4617
|
-
for (const k of warp.keyforms) {
|
|
4618
|
-
pts.forEach(([x, y], i) => {
|
|
4619
|
-
const px = x + k.offsets[2 * i] * bw(box);
|
|
4620
|
-
const py = y + k.offsets[2 * i + 1] * bh(box);
|
|
4621
|
-
l = Math.max(l, box.x0 - px);
|
|
4622
|
-
r = Math.max(r, px - box.x1);
|
|
4623
|
-
b = Math.max(b, box.y0 - py);
|
|
4624
|
-
t = Math.max(t, py - box.y1);
|
|
4625
|
-
});
|
|
4626
|
-
}
|
|
4627
|
-
out.left += l;
|
|
4628
|
-
out.right += r;
|
|
4629
|
-
out.bottom += b;
|
|
4630
|
-
out.top += t;
|
|
4631
|
-
}
|
|
4632
|
-
return out;
|
|
4633
|
-
}
|
|
4634
|
-
function blinkFold(param, mesh, box, travel, eye = box) {
|
|
4635
|
-
const yc = eye.y1 - travel * bh(eye);
|
|
4636
|
-
return localWarp(
|
|
4637
|
-
param,
|
|
4638
|
-
mesh,
|
|
4639
|
-
box,
|
|
4640
|
-
[0, 1],
|
|
4641
|
-
([, y], v) => v === 1 ? [0, 0] : [0, yc - y]
|
|
4642
|
-
);
|
|
4643
|
-
}
|
|
4644
|
-
function lashFold(param, mesh, lash, eye, travel) {
|
|
4645
|
-
const yc = eye.y1 - travel * bh(eye) - 0.04 * bh(eye);
|
|
4646
|
-
const c = cx(lash);
|
|
4647
|
-
const half = bw(lash) / 2;
|
|
4648
|
-
const sag = 0.3 * bh(lash);
|
|
4649
|
-
return localWarp(param, mesh, lash, [0, 1], ([x, y], v) => {
|
|
4650
|
-
if (v === 1) return [0, 0];
|
|
4651
|
-
const u = (x - c) / half;
|
|
4652
|
-
return [0, yc + (y - lash.y0) * 0.5 - sag * (1 - u * u) - y];
|
|
4653
|
-
});
|
|
4654
|
-
}
|
|
4655
|
-
function gazeBindings(irisW, share = 1) {
|
|
4656
|
-
const gx = AMPLITUDE.gazeX * irisW * share;
|
|
4657
|
-
const gy = AMPLITUDE.gazeY * irisW * share;
|
|
4658
|
-
return [
|
|
4659
|
-
{
|
|
4660
|
-
parameter: import_format5.StandardParameter.EyeballX,
|
|
4661
|
-
channel: "translateX",
|
|
4662
|
-
from: -round1(gx),
|
|
4663
|
-
to: round1(gx)
|
|
4664
|
-
},
|
|
4665
|
-
{
|
|
4666
|
-
parameter: import_format5.StandardParameter.EyeballY,
|
|
4667
|
-
channel: "translateY",
|
|
4668
|
-
from: -round1(gy),
|
|
4669
|
-
to: round1(gy)
|
|
4670
|
-
}
|
|
4671
|
-
];
|
|
4672
|
-
}
|
|
4673
|
-
function gazeReach(irisW, share = 1) {
|
|
4674
|
-
return [AMPLITUDE.gazeX * irisW * share, AMPLITUDE.gazeY * irisW * share];
|
|
4675
|
-
}
|
|
4676
|
-
function browReach(hh) {
|
|
4677
|
-
return AMPLITUDE.brow * hh;
|
|
4678
|
-
}
|
|
4679
|
-
function browBindings(side, reach) {
|
|
4680
|
-
const y = side === "L" ? import_format5.StandardParameter.BrowLeftY : import_format5.StandardParameter.BrowRightY;
|
|
4681
|
-
const a = side === "L" ? import_format5.StandardParameter.BrowLeftAngle : import_format5.StandardParameter.BrowRightAngle;
|
|
4682
|
-
return [
|
|
4683
|
-
{
|
|
4684
|
-
parameter: y,
|
|
4685
|
-
channel: "translateY",
|
|
4686
|
-
from: -round1(reach),
|
|
4687
|
-
to: round1(reach)
|
|
4688
|
-
},
|
|
4689
|
-
{ parameter: a, channel: "rotate", from: -BROW_TILT, to: BROW_TILT }
|
|
4690
|
-
];
|
|
4691
|
-
}
|
|
4692
|
-
var BROW_TILT = 15;
|
|
4693
|
-
function mouthForm(mesh, box) {
|
|
4694
|
-
const a = 0.3 * bh(box);
|
|
4695
|
-
const c = cx(box);
|
|
4696
|
-
const half = bw(box) / 2;
|
|
4697
|
-
return localWarp(import_format5.StandardParameter.MouthForm, mesh, box, [-1, 0, 1], ([x], v) => {
|
|
4698
|
-
if (v === 0) return [0, 0];
|
|
4699
|
-
const u = (x - c) / half;
|
|
4700
|
-
const widen = v > 0 ? 0.05 : 0.03;
|
|
4701
|
-
return [v * widen * (x - c), v * a * (u * u - 0.3)];
|
|
4702
|
-
});
|
|
4703
|
-
}
|
|
4704
|
-
function mouthOpenGrow(mesh, box) {
|
|
4705
|
-
const c = cx(box);
|
|
4706
|
-
return localWarp(
|
|
4707
|
-
import_format5.StandardParameter.MouthOpen,
|
|
4708
|
-
mesh,
|
|
4709
|
-
box,
|
|
4710
|
-
[0, 1],
|
|
4711
|
-
([x, y], v) => v === 1 ? [(AMPLITUDE.mouthOpenWidth - 1) * (x - c), 0] : [0, box.y1 + (y - box.y1) * 0.2 - y]
|
|
4712
|
-
);
|
|
4713
|
-
}
|
|
4714
|
-
function mouthWiden(mesh, box) {
|
|
4715
|
-
const c = cx(box);
|
|
4716
|
-
return localWarp(import_format5.StandardParameter.MouthOpen, mesh, box, [0, 1], ([x], v) => [
|
|
4717
|
-
v * (AMPLITUDE.mouthOpenWidth - 1) * (x - c),
|
|
4718
|
-
0
|
|
4719
|
-
]);
|
|
4720
|
-
}
|
|
4721
|
-
function hairSway(param, mesh, box, amp) {
|
|
4722
|
-
return localWarp(
|
|
4723
|
-
param,
|
|
4724
|
-
mesh,
|
|
4725
|
-
box,
|
|
4726
|
-
[-SWAY_RANGE, 0, SWAY_RANGE],
|
|
4727
|
-
([, y], v) => {
|
|
4728
|
-
const t = Math.max(0, (box.y1 - y) / bh(box));
|
|
4729
|
-
return [v / SWAY_RANGE * amp * t ** SWAY_CURVE, 0];
|
|
4730
|
-
}
|
|
4731
|
-
);
|
|
4732
|
-
}
|
|
4733
|
-
function tiltHang(mesh, box, pivot, weight) {
|
|
4734
|
-
return localWarp(import_format5.StandardParameter.AngleZ, mesh, box, Z_STOPS, ([x, y], v, i) => {
|
|
4735
|
-
const g = weight(y, i);
|
|
4736
|
-
if (g === 0 || v === 0) return [0, 0];
|
|
4737
|
-
const a = -g * rollOf(v);
|
|
4738
|
-
const px = x - pivot[0];
|
|
4739
|
-
const py = y - pivot[1];
|
|
4740
|
-
return [
|
|
4741
|
-
pivot[0] + px * Math.cos(a) - py * Math.sin(a) - x,
|
|
4742
|
-
pivot[1] + px * Math.sin(a) + py * Math.cos(a) - y
|
|
4743
|
-
];
|
|
4744
|
-
});
|
|
4745
|
-
}
|
|
4746
|
-
function hairHangWeight(pivotY, bottom) {
|
|
4747
|
-
if (bottom >= pivotY) return () => 0;
|
|
4748
|
-
return (y) => HAIR_HANG * smoothstep(pivotY, bottom, y);
|
|
4749
|
-
}
|
|
4750
|
-
var round1 = (v) => roundTo(v, 0.1);
|
|
4751
|
-
|
|
4752
5215
|
// src/auto-rig/generate.ts
|
|
4753
5216
|
var MESH_CELLS = {
|
|
4754
5217
|
face: { px: 28, min: 4, max: 24 },
|
|
@@ -4775,6 +5238,7 @@ var WARP_ID = {
|
|
|
4775
5238
|
mouth: "mouthWarp"
|
|
4776
5239
|
};
|
|
4777
5240
|
var isGridFamily = (f) => f in WARP_ID;
|
|
5241
|
+
var isArm = (spec) => spec.family === "arm";
|
|
4778
5242
|
var GRID_PAD = 6;
|
|
4779
5243
|
function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
4780
5244
|
checkLayers(layers, canvas);
|
|
@@ -4823,6 +5287,18 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
4823
5287
|
chinSlide: chinSlide(fit.model)
|
|
4824
5288
|
});
|
|
4825
5289
|
const turn = { ...fit.model, frame };
|
|
5290
|
+
const arms = /* @__PURE__ */ new Map();
|
|
5291
|
+
for (const spec of ROLE_TABLE) {
|
|
5292
|
+
const layer = byRole.get(spec.role);
|
|
5293
|
+
if (layer === void 0 || !isArm(spec)) continue;
|
|
5294
|
+
arms.set(spec.role, armGeometry(layer, cx(box("body"))));
|
|
5295
|
+
}
|
|
5296
|
+
const bodyWarp = has("body") ? buildBodyWarp({
|
|
5297
|
+
body: byRole.get("body"),
|
|
5298
|
+
chin: { x: frame.axisX, y: frame.chinY },
|
|
5299
|
+
hh: frame.hh,
|
|
5300
|
+
pivots: [...arms.values()].map((g) => g.shoulder)
|
|
5301
|
+
}) : void 0;
|
|
4826
5302
|
const parts = [];
|
|
4827
5303
|
const reach = /* @__PURE__ */ new Map();
|
|
4828
5304
|
let regionOf = [];
|
|
@@ -4830,6 +5306,12 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
4830
5306
|
for (const spec of ROLE_TABLE) {
|
|
4831
5307
|
const layer = byRole.get(spec.role);
|
|
4832
5308
|
if (layer === void 0) continue;
|
|
5309
|
+
if (isArm(spec)) {
|
|
5310
|
+
parts.push(
|
|
5311
|
+
...armParts(spec.role, layer, arms.get(spec.role), parts.length)
|
|
5312
|
+
);
|
|
5313
|
+
continue;
|
|
5314
|
+
}
|
|
4833
5315
|
const built = buildPart(
|
|
4834
5316
|
spec,
|
|
4835
5317
|
boxOfLayer(layer),
|
|
@@ -4848,7 +5330,9 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
4848
5330
|
}
|
|
4849
5331
|
const grids = /* @__PURE__ */ new Map();
|
|
4850
5332
|
for (const family of Object.keys(WARP_ID)) {
|
|
4851
|
-
const members = parts.filter(
|
|
5333
|
+
const members = parts.filter(
|
|
5334
|
+
(p) => roleSpec(roleOfPart(p.id)).family === family
|
|
5335
|
+
);
|
|
4852
5336
|
if (members.length === 0) continue;
|
|
4853
5337
|
const area = unionBoxes(
|
|
4854
5338
|
members.map((p) => {
|
|
@@ -4870,13 +5354,19 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
4870
5354
|
});
|
|
4871
5355
|
}
|
|
4872
5356
|
for (const part of parts) {
|
|
4873
|
-
const family = roleSpec(part.id).family;
|
|
5357
|
+
const family = roleSpec(roleOfPart(part.id)).family;
|
|
4874
5358
|
if (isGridFamily(family)) {
|
|
4875
5359
|
part.deformer = WARP_ID[family];
|
|
4876
5360
|
continue;
|
|
4877
5361
|
}
|
|
4878
|
-
|
|
4879
|
-
|
|
5362
|
+
if (family === "body") {
|
|
5363
|
+
part.deformer = BODY_WARP_ID;
|
|
5364
|
+
part.mesh = bodyWarp.mesh;
|
|
5365
|
+
continue;
|
|
5366
|
+
}
|
|
5367
|
+
if (family === "arm") continue;
|
|
5368
|
+
part.deformer = "headDeformer";
|
|
5369
|
+
if (part.mesh === void 0) continue;
|
|
4880
5370
|
const own = familyField(turn, family);
|
|
4881
5371
|
const fieldOf = part.id === "face" ? plateFields(turn, regionOf, own) : () => own;
|
|
4882
5372
|
part.warps = [
|
|
@@ -4886,17 +5376,17 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
4886
5376
|
}
|
|
4887
5377
|
const parameters = declareParameters(new Set(byRole.keys()));
|
|
4888
5378
|
const model = {
|
|
4889
|
-
version:
|
|
5379
|
+
version: import_format8.IKI_FORMAT_VERSION,
|
|
4890
5380
|
name: "auto-rigged",
|
|
4891
5381
|
canvas: { width: canvas.width, height: canvas.height },
|
|
4892
5382
|
parameters,
|
|
4893
5383
|
parts,
|
|
4894
|
-
deformers: deformers(frame, grids,
|
|
5384
|
+
deformers: deformers(frame, grids, bodyWarp?.deformer, arms),
|
|
4895
5385
|
...has("hair_front") ? { physics: hairPhysics() } : {},
|
|
4896
5386
|
expressions: defaultExpressions(new Set(parameters.map((p) => p.id))),
|
|
4897
5387
|
motions: DEFAULT_MOTIONS
|
|
4898
5388
|
};
|
|
4899
|
-
const valid = (0,
|
|
5389
|
+
const valid = (0, import_format8.parseIkiModel)(model);
|
|
4900
5390
|
if (hasNose && options.onTurnSolved) {
|
|
4901
5391
|
options.onTurnSolved(
|
|
4902
5392
|
turnReport(
|
|
@@ -4911,14 +5401,14 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
4911
5401
|
function turnWarps(mesh, b, fieldOf) {
|
|
4912
5402
|
return [
|
|
4913
5403
|
localWarp(
|
|
4914
|
-
|
|
5404
|
+
import_format8.StandardParameter.AngleX,
|
|
4915
5405
|
mesh,
|
|
4916
5406
|
b,
|
|
4917
5407
|
X_STOPS,
|
|
4918
5408
|
([x, y], v, i) => v === 0 ? [0, 0] : [fieldOf(i)(x, y, v, 0)[0], 0]
|
|
4919
5409
|
),
|
|
4920
5410
|
localWarp(
|
|
4921
|
-
|
|
5411
|
+
import_format8.StandardParameter.AngleY,
|
|
4922
5412
|
mesh,
|
|
4923
5413
|
b,
|
|
4924
5414
|
Y_STOPS,
|
|
@@ -4966,16 +5456,22 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
4966
5456
|
}
|
|
4967
5457
|
if (mesh !== void 0) part.mesh = mesh;
|
|
4968
5458
|
const side = spec.role.endsWith("_L") ? "L" : "R";
|
|
4969
|
-
const eyeOpen = side === "L" ?
|
|
5459
|
+
const eyeOpen = side === "L" ? import_format8.StandardParameter.EyeOpenLeft : import_format8.StandardParameter.EyeOpenRight;
|
|
4970
5460
|
const hh = frame.hh;
|
|
4971
5461
|
switch (spec.role) {
|
|
4972
5462
|
case "face":
|
|
4973
5463
|
if (region?.some((r) => r === "neck")) {
|
|
4974
5464
|
const isNeck = region.map((r) => r === "neck");
|
|
4975
5465
|
warps.push(
|
|
4976
|
-
tiltHang(
|
|
5466
|
+
tiltHang(
|
|
5467
|
+
mesh,
|
|
5468
|
+
b,
|
|
5469
|
+
pivot,
|
|
5470
|
+
(_y, i) => isNeck[i] ? 1 : 0,
|
|
5471
|
+
headOwnRoll(byRole.has("body"))
|
|
5472
|
+
),
|
|
4977
5473
|
localWarp(
|
|
4978
|
-
|
|
5474
|
+
import_format8.StandardParameter.Breath,
|
|
4979
5475
|
mesh,
|
|
4980
5476
|
b,
|
|
4981
5477
|
[0, 1],
|
|
@@ -5022,8 +5518,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5022
5518
|
if (byRole.has("mouth_open")) {
|
|
5023
5519
|
warps.push(mouthWiden(mesh, b));
|
|
5024
5520
|
bindings.push(
|
|
5025
|
-
{ parameter:
|
|
5026
|
-
{ parameter:
|
|
5521
|
+
{ parameter: import_format8.StandardParameter.MouthOpen, channel: "opacity", from: 1, to: 0 },
|
|
5522
|
+
{ parameter: import_format8.StandardParameter.MouthOpen, channel: "opacity", from: 1, to: 0 }
|
|
5027
5523
|
);
|
|
5028
5524
|
extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
|
|
5029
5525
|
} else {
|
|
@@ -5033,19 +5529,19 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5033
5529
|
);
|
|
5034
5530
|
bindings.push(
|
|
5035
5531
|
{
|
|
5036
|
-
parameter:
|
|
5532
|
+
parameter: import_format8.StandardParameter.MouthOpen,
|
|
5037
5533
|
channel: "scaleY",
|
|
5038
5534
|
from: 0,
|
|
5039
5535
|
to: roundTo(grow, 0.01)
|
|
5040
5536
|
},
|
|
5041
5537
|
{
|
|
5042
|
-
parameter:
|
|
5538
|
+
parameter: import_format8.StandardParameter.MouthOpen,
|
|
5043
5539
|
channel: "scaleX",
|
|
5044
5540
|
from: 0,
|
|
5045
5541
|
to: roundTo(AMPLITUDE.mouthOpenWidth - 1, 0.01)
|
|
5046
5542
|
},
|
|
5047
5543
|
{
|
|
5048
|
-
parameter:
|
|
5544
|
+
parameter: import_format8.StandardParameter.MouthOpen,
|
|
5049
5545
|
channel: "translateY",
|
|
5050
5546
|
from: 0,
|
|
5051
5547
|
to: -roundTo(grow * bh(b) / 2, 0.01)
|
|
@@ -5057,7 +5553,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5057
5553
|
case "mouth_open":
|
|
5058
5554
|
warps.push(mouthForm(mesh, b), mouthOpenGrow(mesh, b));
|
|
5059
5555
|
bindings.push({
|
|
5060
|
-
parameter:
|
|
5556
|
+
parameter: import_format8.StandardParameter.MouthOpen,
|
|
5061
5557
|
channel: "opacity",
|
|
5062
5558
|
from: 0,
|
|
5063
5559
|
to: 1
|
|
@@ -5067,7 +5563,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5067
5563
|
case "blush_L":
|
|
5068
5564
|
case "blush_R":
|
|
5069
5565
|
bindings.push({
|
|
5070
|
-
parameter:
|
|
5566
|
+
parameter: import_format8.StandardParameter.Cheek,
|
|
5071
5567
|
channel: "opacity",
|
|
5072
5568
|
from: AMPLITUDE.blushRest,
|
|
5073
5569
|
to: 1
|
|
@@ -5078,8 +5574,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5078
5574
|
if (byRole.has("hair_front")) {
|
|
5079
5575
|
const amp = style.sway * AMPLITUDE.sway * hh;
|
|
5080
5576
|
warps.push(
|
|
5081
|
-
hairSway(
|
|
5082
|
-
hairSway(
|
|
5577
|
+
hairSway(import_format8.StandardParameter.HairSwayX, mesh, b, amp),
|
|
5578
|
+
hairSway(import_format8.StandardParameter.HairSwayZ, mesh, b, amp)
|
|
5083
5579
|
);
|
|
5084
5580
|
}
|
|
5085
5581
|
warps.push(tiltHang(mesh, b, pivot, hairHangWeight(frame.chinY, b.y0)));
|
|
@@ -5152,33 +5648,24 @@ function meshScale(role, frame) {
|
|
|
5152
5648
|
return Infinity;
|
|
5153
5649
|
}
|
|
5154
5650
|
}
|
|
5155
|
-
function deformers(frame, grids,
|
|
5651
|
+
function deformers(frame, grids, bodyWarp, arms) {
|
|
5156
5652
|
const out = [];
|
|
5157
|
-
if (
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
bindings: [
|
|
5162
|
-
{
|
|
5163
|
-
parameter: import_format6.StandardParameter.Breath,
|
|
5164
|
-
channel: "translateY",
|
|
5165
|
-
from: 0,
|
|
5166
|
-
to: roundTo(AMPLITUDE.breathBody * frame.hh, 0.01)
|
|
5167
|
-
}
|
|
5168
|
-
]
|
|
5169
|
-
});
|
|
5170
|
-
}
|
|
5653
|
+
if (bodyWarp !== void 0) out.push(bodyWarp);
|
|
5654
|
+
for (const [role, g] of arms) out.push(...armDeformers(role, g));
|
|
5655
|
+
const hasBody = bodyWarp !== void 0;
|
|
5656
|
+
const roll = headOwnRoll(hasBody);
|
|
5171
5657
|
out.push({
|
|
5172
5658
|
id: "headDeformer",
|
|
5659
|
+
...hasBody ? { parent: BODY_WARP_ID } : {},
|
|
5173
5660
|
pivot: { x: roundTo(frame.axisX, 0.01), y: roundTo(frame.chinY, 0.01) },
|
|
5174
5661
|
bindings: [
|
|
5175
5662
|
// AngleZ is clockwise-positive (Live2D's); a rotation is CCW-positive.
|
|
5176
|
-
{ parameter:
|
|
5663
|
+
{ parameter: import_format8.StandardParameter.AngleZ, channel: "rotate", from: roll, to: -roll },
|
|
5177
5664
|
{
|
|
5178
|
-
parameter:
|
|
5665
|
+
parameter: import_format8.StandardParameter.Breath,
|
|
5179
5666
|
channel: "translateY",
|
|
5180
5667
|
from: 0,
|
|
5181
|
-
to: roundTo(
|
|
5668
|
+
to: roundTo(headOwnBreath(hasBody) * frame.hh, 0.01)
|
|
5182
5669
|
}
|
|
5183
5670
|
]
|
|
5184
5671
|
});
|
|
@@ -5193,8 +5680,8 @@ function deformers(frame, grids, body) {
|
|
|
5193
5680
|
points: g.lattice.points
|
|
5194
5681
|
},
|
|
5195
5682
|
warp2d: {
|
|
5196
|
-
parameter:
|
|
5197
|
-
parameterY:
|
|
5683
|
+
parameter: import_format8.StandardParameter.AngleX,
|
|
5684
|
+
parameterY: import_format8.StandardParameter.AngleY,
|
|
5198
5685
|
valuesX: X_STOPS,
|
|
5199
5686
|
valuesY: Y_STOPS,
|
|
5200
5687
|
keyforms2d: g.keyforms.map((offsets) => ({ offsets }))
|
|
@@ -5207,16 +5694,16 @@ function hairPhysics() {
|
|
|
5207
5694
|
return [
|
|
5208
5695
|
{
|
|
5209
5696
|
id: "hairSway",
|
|
5210
|
-
input: { parameter:
|
|
5211
|
-
output: { parameter:
|
|
5697
|
+
input: { parameter: import_format8.StandardParameter.AngleX, weight: 1 },
|
|
5698
|
+
output: { parameter: import_format8.StandardParameter.HairSwayX, scale: 5 },
|
|
5212
5699
|
mass: 1,
|
|
5213
5700
|
stiffness: 30,
|
|
5214
5701
|
damping: 3
|
|
5215
5702
|
},
|
|
5216
5703
|
{
|
|
5217
5704
|
id: "hairTilt",
|
|
5218
|
-
input: { parameter:
|
|
5219
|
-
output: { parameter:
|
|
5705
|
+
input: { parameter: import_format8.StandardParameter.AngleZ, weight: 1 },
|
|
5706
|
+
output: { parameter: import_format8.StandardParameter.HairSwayZ, scale: 8 },
|
|
5220
5707
|
mass: 1,
|
|
5221
5708
|
stiffness: 25,
|
|
5222
5709
|
damping: 2.5
|
|
@@ -5226,32 +5713,46 @@ function hairPhysics() {
|
|
|
5226
5713
|
function declareParameters(roles) {
|
|
5227
5714
|
const out = [];
|
|
5228
5715
|
const add = (id, name, min, max, def) => out.push({ id, name, min, max, default: def });
|
|
5229
|
-
add(
|
|
5230
|
-
add(
|
|
5231
|
-
add(
|
|
5232
|
-
|
|
5233
|
-
|
|
5716
|
+
add(import_format8.StandardParameter.AngleX, "Head Angle", -30, 30, 0);
|
|
5717
|
+
add(import_format8.StandardParameter.AngleY, "Head Angle Y", -30, 30, 0);
|
|
5718
|
+
add(import_format8.StandardParameter.AngleZ, "Head Angle Z", -30, 30, 0);
|
|
5719
|
+
if (roles.has("body")) {
|
|
5720
|
+
add(import_format8.StandardParameter.BodyAngleX, "Body Angle X", -10, 10, 0);
|
|
5721
|
+
add(import_format8.StandardParameter.BodyAngleY, "Body Angle Y", -10, 10, 0);
|
|
5722
|
+
add(import_format8.StandardParameter.BodyAngleZ, "Body Angle Z", -10, 10, 0);
|
|
5723
|
+
}
|
|
5724
|
+
const [armMin, armMax] = ARM_RANGE;
|
|
5725
|
+
if (roles.has("arm_L")) {
|
|
5726
|
+
add(import_format8.StandardParameter.ArmLeft, "Arm L", armMin, armMax, 0);
|
|
5727
|
+
add(import_format8.StandardParameter.ElbowLeft, "Elbow L", armMin, armMax, 0);
|
|
5728
|
+
}
|
|
5729
|
+
if (roles.has("arm_R")) {
|
|
5730
|
+
add(import_format8.StandardParameter.ArmRight, "Arm R", armMin, armMax, 0);
|
|
5731
|
+
add(import_format8.StandardParameter.ElbowRight, "Elbow R", armMin, armMax, 0);
|
|
5732
|
+
}
|
|
5733
|
+
add(import_format8.StandardParameter.EyeOpenLeft, "Eye L", 0, 1, 1);
|
|
5734
|
+
add(import_format8.StandardParameter.EyeOpenRight, "Eye R", 0, 1, 1);
|
|
5234
5735
|
if ([...roles].some((r) => /^(iris|pupil|highlight)_/.test(r))) {
|
|
5235
|
-
add(
|
|
5236
|
-
add(
|
|
5736
|
+
add(import_format8.StandardParameter.EyeballX, "Gaze X", -1, 1, 0);
|
|
5737
|
+
add(import_format8.StandardParameter.EyeballY, "Gaze Y", -1, 1, 0);
|
|
5237
5738
|
}
|
|
5238
|
-
add(
|
|
5239
|
-
add(
|
|
5739
|
+
add(import_format8.StandardParameter.MouthOpen, "Mouth Open", 0, 1, 0);
|
|
5740
|
+
add(import_format8.StandardParameter.MouthForm, "Mouth Form", -1, 1, 0);
|
|
5240
5741
|
if (roles.has("brow_L")) {
|
|
5241
|
-
add(
|
|
5242
|
-
add(
|
|
5742
|
+
add(import_format8.StandardParameter.BrowLeftY, "Brow L Y", -1, 1, 0);
|
|
5743
|
+
add(import_format8.StandardParameter.BrowLeftAngle, "Brow L Angle", -1, 1, 0);
|
|
5243
5744
|
}
|
|
5244
5745
|
if (roles.has("brow_R")) {
|
|
5245
|
-
add(
|
|
5246
|
-
add(
|
|
5746
|
+
add(import_format8.StandardParameter.BrowRightY, "Brow R Y", -1, 1, 0);
|
|
5747
|
+
add(import_format8.StandardParameter.BrowRightAngle, "Brow R Angle", -1, 1, 0);
|
|
5247
5748
|
}
|
|
5248
5749
|
if (roles.has("blush_L") || roles.has("blush_R")) {
|
|
5249
|
-
add(
|
|
5750
|
+
add(import_format8.StandardParameter.Cheek, "Cheek", 0, 1, 0);
|
|
5250
5751
|
}
|
|
5251
|
-
add(
|
|
5752
|
+
add(import_format8.StandardParameter.Breath, "Breath", 0, 1, 0);
|
|
5252
5753
|
if (roles.has("hair_front")) {
|
|
5253
|
-
add(
|
|
5254
|
-
add(
|
|
5754
|
+
add(import_format8.StandardParameter.HairSwayX, "Hair Sway X", -SWAY_RANGE, SWAY_RANGE, 0);
|
|
5755
|
+
add(import_format8.StandardParameter.HairSwayZ, "Hair Sway Z", -SWAY_RANGE, SWAY_RANGE, 0);
|
|
5255
5756
|
}
|
|
5256
5757
|
return out;
|
|
5257
5758
|
}
|
|
@@ -5270,6 +5771,7 @@ function declareParameters(roles) {
|
|
|
5270
5771
|
DeletePhysicsRig,
|
|
5271
5772
|
EditorDocument,
|
|
5272
5773
|
HEAD_BAND,
|
|
5774
|
+
LayerGeometryError,
|
|
5273
5775
|
SPECK_CORE_FRACTION,
|
|
5274
5776
|
SetDeformerBindings,
|
|
5275
5777
|
SetDeformerParent,
|
|
@@ -5304,6 +5806,7 @@ function declareParameters(roles) {
|
|
|
5304
5806
|
isSpeckCore,
|
|
5305
5807
|
packAtlas,
|
|
5306
5808
|
parseLayerRoles,
|
|
5809
|
+
partIdsOfRole,
|
|
5307
5810
|
upsertGridKeyform,
|
|
5308
5811
|
uvRectFor,
|
|
5309
5812
|
validateDeformerDelete,
|