@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.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,
|
|
@@ -721,6 +723,12 @@ var CaptureGridKeyform = class {
|
|
|
721
723
|
prevKeyforms;
|
|
722
724
|
apply(doc) {
|
|
723
725
|
const deformer = doc.findWarpDeformer(this.deformerId);
|
|
726
|
+
const count = deformer.warps?.length ?? 0;
|
|
727
|
+
if (count > 1) {
|
|
728
|
+
throw new Error(
|
|
729
|
+
`deformers."${this.deformerId}".warps: ${count} grid warps \u2014 capture targets a deformer with exactly one grid warp (which entry to write into is ambiguous)`
|
|
730
|
+
);
|
|
731
|
+
}
|
|
724
732
|
const warp = deformer.warps?.[0];
|
|
725
733
|
if (!warp) {
|
|
726
734
|
throw new Error(
|
|
@@ -1626,6 +1634,14 @@ function facePlateHalfOf(layers) {
|
|
|
1626
1634
|
return face.cropW / 2;
|
|
1627
1635
|
}
|
|
1628
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
|
+
]);
|
|
1629
1645
|
function createLayerSetMeasurer(canvas) {
|
|
1630
1646
|
for (const field of ["width", "height"]) {
|
|
1631
1647
|
const value = canvas[field];
|
|
@@ -1655,7 +1671,7 @@ function createLayerSetMeasurer(canvas) {
|
|
|
1655
1671
|
if (bbox === null) return null;
|
|
1656
1672
|
const rowLeft = new Int32Array(canvasH).fill(canvasW);
|
|
1657
1673
|
const rowRight = new Int32Array(canvasH).fill(-1);
|
|
1658
|
-
const rowRuns = role
|
|
1674
|
+
const rowRuns = ROW_RUN_ROLES.has(role) ? [] : void 0;
|
|
1659
1675
|
for (let y = 0; y < canvasH; y++) {
|
|
1660
1676
|
let runs;
|
|
1661
1677
|
if (rowRuns !== void 0 && y >= bbox.y && y < bbox.y + bbox.h) {
|
|
@@ -1997,7 +2013,13 @@ var PROFILE_VALUES = {
|
|
|
1997
2013
|
brow: 0.15,
|
|
1998
2014
|
breath: 0.03,
|
|
1999
2015
|
sway: 0.08,
|
|
2000
|
-
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
|
|
2001
2023
|
};
|
|
2002
2024
|
function deriveProfile(v) {
|
|
2003
2025
|
const farLead = v.lead * (1 - v.fore / 2);
|
|
@@ -2091,9 +2113,32 @@ function deriveProfile(v) {
|
|
|
2091
2113
|
* slide, to the next 0.05. */
|
|
2092
2114
|
hiddenNeck: Math.ceil(30 * (v.slide + v.lead) - 1e-9) / 20
|
|
2093
2115
|
};
|
|
2094
|
-
|
|
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
|
+
};
|
|
2095
2140
|
}
|
|
2096
|
-
var { TURN, NOD, ROLL_DEG, AMPLITUDE } = deriveProfile(PROFILE_VALUES);
|
|
2141
|
+
var { TURN, NOD, ROLL_DEG, AMPLITUDE, BODY } = deriveProfile(PROFILE_VALUES);
|
|
2097
2142
|
var STYLE_KNOBS = [
|
|
2098
2143
|
"turn",
|
|
2099
2144
|
"featureLead",
|
|
@@ -2123,6 +2168,12 @@ var TurnTargetError = class extends Error {
|
|
|
2123
2168
|
this.name = "TurnTargetError";
|
|
2124
2169
|
}
|
|
2125
2170
|
};
|
|
2171
|
+
var LayerGeometryError = class extends Error {
|
|
2172
|
+
constructor(message) {
|
|
2173
|
+
super(message);
|
|
2174
|
+
this.name = "LayerGeometryError";
|
|
2175
|
+
}
|
|
2176
|
+
};
|
|
2126
2177
|
var DEFAULT_TURN_TARGETS = Object.freeze({
|
|
2127
2178
|
eyeShift: round2((TURN.eyeFar + TURN.eyeNear) / 2 - TURN.hairBack),
|
|
2128
2179
|
farEyeRatio: round2(TURN.eyeFarScale / TURN.eyeNearScale),
|
|
@@ -2133,6 +2184,8 @@ var DEFAULT_TURN_TARGETS = Object.freeze({
|
|
|
2133
2184
|
var ROLE_TABLE = [
|
|
2134
2185
|
{ role: "hair_back", family: "hair_back" },
|
|
2135
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"] },
|
|
2136
2189
|
{ role: "face", family: "face", required: true },
|
|
2137
2190
|
{ role: "blush_L", family: "face" },
|
|
2138
2191
|
{ role: "blush_R", family: "face" },
|
|
@@ -2164,6 +2217,15 @@ function roleSpec(role) {
|
|
|
2164
2217
|
if (spec === void 0) throw new Error(`auto-rig: unknown role "${role}"`);
|
|
2165
2218
|
return spec;
|
|
2166
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
|
+
}
|
|
2167
2229
|
var ALIASES = {
|
|
2168
2230
|
eyebrow_L: "brow_L",
|
|
2169
2231
|
eyebrow_R: "brow_R",
|
|
@@ -2174,6 +2236,16 @@ function roleOf(fileName) {
|
|
|
2174
2236
|
const role = fileName.replace(/\.[^.]+$/, "").toLowerCase().replace(/[-\s]+/g, "_").replace(/_([lr])$/, (_, s) => `_${s.toUpperCase()}`);
|
|
2175
2237
|
return ALIASES[role] ?? role;
|
|
2176
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
|
+
}
|
|
2177
2249
|
function parseLayerRoles(fileNames) {
|
|
2178
2250
|
const seen = /* @__PURE__ */ new Map();
|
|
2179
2251
|
const out = [];
|
|
@@ -2199,11 +2271,12 @@ function parseLayerRoles(fileNames) {
|
|
|
2199
2271
|
`auto-rig: missing required role${missing.length > 1 ? "s" : ""} ${missing.join(", ")}`
|
|
2200
2272
|
);
|
|
2201
2273
|
}
|
|
2274
|
+
checkArmsHaveBody(seen.keys());
|
|
2202
2275
|
return out;
|
|
2203
2276
|
}
|
|
2204
2277
|
|
|
2205
2278
|
// src/auto-rig/generate.ts
|
|
2206
|
-
var
|
|
2279
|
+
var import_format8 = require("@ikijs/format");
|
|
2207
2280
|
|
|
2208
2281
|
// src/auto-rig/animations.ts
|
|
2209
2282
|
var import_format3 = require("@ikijs/format");
|
|
@@ -2354,6 +2427,12 @@ var DEFAULT_MOTIONS = {
|
|
|
2354
2427
|
]
|
|
2355
2428
|
};
|
|
2356
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
|
+
|
|
2357
2436
|
// src/auto-rig/layout.ts
|
|
2358
2437
|
function boxOfLayer(layer) {
|
|
2359
2438
|
return boxOfPixels(layer.bbox, layer.canvasW, layer.canvasH);
|
|
@@ -2427,6 +2506,651 @@ function meshPoints(mesh, box) {
|
|
|
2427
2506
|
return out;
|
|
2428
2507
|
}
|
|
2429
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
|
+
|
|
2430
3154
|
// src/auto-rig/checks.ts
|
|
2431
3155
|
var finite = (v) => typeof v === "number" && Number.isFinite(v);
|
|
2432
3156
|
var isObject = (v) => typeof v === "object" && v !== null;
|
|
@@ -2530,6 +3254,7 @@ function checkLayers(layers, canvas) {
|
|
|
2530
3254
|
if (missing.length > 0) {
|
|
2531
3255
|
throw new Error(`auto-rig: missing required role(s) ${missing.join(", ")}`);
|
|
2532
3256
|
}
|
|
3257
|
+
checkArmsHaveBody(seen);
|
|
2533
3258
|
}
|
|
2534
3259
|
function checkHeadEdges(edges, byRole) {
|
|
2535
3260
|
if (edges === void 0) return;
|
|
@@ -2610,131 +3335,45 @@ function checkStrandEdges(strand, irises, hair) {
|
|
|
2610
3335
|
for (const [k, x] of [
|
|
2611
3336
|
["irisOuter", outer],
|
|
2612
3337
|
["irisInner", inner]
|
|
2613
|
-
]) {
|
|
2614
|
-
if (x < iris.x0 || x > iris.x1) {
|
|
2615
|
-
throw new TurnTargetError(`${at}.${k} ${x} is off its iris's crop`);
|
|
2616
|
-
}
|
|
2617
|
-
}
|
|
2618
|
-
if (sign * (outer - inner) <= 0) {
|
|
2619
|
-
throw new TurnTargetError(
|
|
2620
|
-
`${at}.irisOuter ${outer} is not strictly outward of irisInner ${inner}`
|
|
2621
|
-
);
|
|
2622
|
-
}
|
|
2623
|
-
for (const [k, x] of [
|
|
2624
|
-
["runOuter", runOuter],
|
|
2625
|
-
["runFace", runFace]
|
|
2626
|
-
]) {
|
|
2627
|
-
if (x !== null && (x < hair.x0 || x > hair.x1)) {
|
|
2628
|
-
throw new TurnTargetError(`${at}.${k} ${x} is off hair_front's crop`);
|
|
2629
|
-
}
|
|
2630
|
-
}
|
|
2631
|
-
if (sign * (runOuter - cx(iris)) <= 0) {
|
|
2632
|
-
throw new TurnTargetError(
|
|
2633
|
-
`${at}.runOuter ${runOuter} is not strictly outward of its iris's centre`
|
|
2634
|
-
);
|
|
2635
|
-
}
|
|
2636
|
-
if (runFace !== null && sign * (runOuter - runFace) <= 0) {
|
|
2637
|
-
throw new TurnTargetError(
|
|
2638
|
-
`${at}.runOuter ${runOuter} is not strictly outward of runFace ${runFace}`
|
|
2639
|
-
);
|
|
2640
|
-
}
|
|
2641
|
-
if (runFace !== null && sign * (runFace - cx(other)) <= 0) {
|
|
2642
|
-
throw new TurnTargetError(
|
|
2643
|
-
`${at}.runFace ${runFace} reaches the other iris's centre; a run that does is a fringe, runFace null`
|
|
2644
|
-
);
|
|
2645
|
-
}
|
|
2646
|
-
}
|
|
2647
|
-
}
|
|
2648
|
-
|
|
2649
|
-
// src/auto-rig/context.ts
|
|
2650
|
-
var import_format4 = require("@ikijs/format");
|
|
2651
|
-
|
|
2652
|
-
// src/auto-rig/grid.ts
|
|
2653
|
-
var X_STOPS = [-30, 0, 30];
|
|
2654
|
-
var Y_STOPS = [-30, 0, 30];
|
|
2655
|
-
function lattice(box, cell, min, max) {
|
|
2656
|
-
const cols = cellsFor(box.x1 - box.x0, cell, min, max);
|
|
2657
|
-
const rows = cellsFor(box.y1 - box.y0, cell, min, max);
|
|
2658
|
-
const points = [];
|
|
2659
|
-
for (let r = 0; r <= rows; r++) {
|
|
2660
|
-
const y = box.y1 - (box.y1 - box.y0) * r / rows;
|
|
2661
|
-
for (let c = 0; c <= cols; c++) {
|
|
2662
|
-
points.push(
|
|
2663
|
-
roundTo(box.x0 + (box.x1 - box.x0) * c / cols, 0.01),
|
|
2664
|
-
roundTo(y, 0.01)
|
|
2665
|
-
);
|
|
2666
|
-
}
|
|
2667
|
-
}
|
|
2668
|
-
return { box, cols, rows, points };
|
|
2669
|
-
}
|
|
2670
|
-
function bake(l, field) {
|
|
2671
|
-
const out = [];
|
|
2672
|
-
for (const ay of Y_STOPS) {
|
|
2673
|
-
for (const ax of X_STOPS) {
|
|
2674
|
-
const offsets = [];
|
|
2675
|
-
for (let k = 0; k < l.points.length; k += 2) {
|
|
2676
|
-
if (ax === 0 && ay === 0) {
|
|
2677
|
-
offsets.push(0, 0);
|
|
2678
|
-
continue;
|
|
2679
|
-
}
|
|
2680
|
-
const [dx, dy] = field(l.points[k], l.points[k + 1], ax, ay);
|
|
2681
|
-
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`);
|
|
2682
3341
|
}
|
|
2683
|
-
out.push(offsets);
|
|
2684
3342
|
}
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
}
|
|
2688
|
-
|
|
2689
|
-
const j = Y_STOPS.indexOf(0);
|
|
2690
|
-
const row = X_STOPS.map((_, i) => keyforms[j * X_STOPS.length + i]);
|
|
2691
|
-
return blendStops(X_STOPS, row, ax);
|
|
2692
|
-
}
|
|
2693
|
-
function blendStops(values, offsets, v) {
|
|
2694
|
-
const x = clamp(v, values[0], values[values.length - 1]);
|
|
2695
|
-
let i = 0;
|
|
2696
|
-
while (i < values.length - 2 && x > values[i + 1]) i++;
|
|
2697
|
-
const t = (x - values[i]) / (values[i + 1] - values[i]);
|
|
2698
|
-
return offsets[i].map((o, k) => o + (offsets[i + 1][k] - o) * t);
|
|
2699
|
-
}
|
|
2700
|
-
function land(l, offsets, x, y) {
|
|
2701
|
-
const { cols, rows, points } = l;
|
|
2702
|
-
const stride = cols + 1;
|
|
2703
|
-
let col = cols - 1;
|
|
2704
|
-
for (let c = 0; c < cols; c++) {
|
|
2705
|
-
if (x < points[(c + 1) * 2]) {
|
|
2706
|
-
col = c;
|
|
2707
|
-
break;
|
|
3343
|
+
if (sign * (outer - inner) <= 0) {
|
|
3344
|
+
throw new TurnTargetError(
|
|
3345
|
+
`${at}.irisOuter ${outer} is not strictly outward of irisInner ${inner}`
|
|
3346
|
+
);
|
|
2708
3347
|
}
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
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
|
+
);
|
|
2715
3370
|
}
|
|
2716
3371
|
}
|
|
2717
|
-
const x0 = points[col * 2];
|
|
2718
|
-
const x1 = points[(col + 1) * 2];
|
|
2719
|
-
const yT = points[row * stride * 2 + 1];
|
|
2720
|
-
const yB = points[(row + 1) * stride * 2 + 1];
|
|
2721
|
-
const s = clamp((x - x0) / (x1 - x0), 0, 1);
|
|
2722
|
-
const t = clamp((yT - y) / (yT - yB), 0, 1);
|
|
2723
|
-
const at = (r, c) => {
|
|
2724
|
-
const k = (r * stride + c) * 2;
|
|
2725
|
-
return [points[k] + offsets[k], points[k + 1] + offsets[k + 1]];
|
|
2726
|
-
};
|
|
2727
|
-
const p00 = at(row, col);
|
|
2728
|
-
const p10 = at(row, col + 1);
|
|
2729
|
-
const p01 = at(row + 1, col);
|
|
2730
|
-
const p11 = at(row + 1, col + 1);
|
|
2731
|
-
const topX = p00[0] + (p10[0] - p00[0]) * s;
|
|
2732
|
-
const topY = p00[1] + (p10[1] - p00[1]) * s;
|
|
2733
|
-
const botX = p01[0] + (p11[0] - p01[0]) * s;
|
|
2734
|
-
const botY = p01[1] + (p11[1] - p01[1]) * s;
|
|
2735
|
-
return [topX + (botX - topX) * t, topY + (botY - topY) * t];
|
|
2736
3372
|
}
|
|
2737
3373
|
|
|
3374
|
+
// src/auto-rig/context.ts
|
|
3375
|
+
var import_format7 = require("@ikijs/format");
|
|
3376
|
+
|
|
2738
3377
|
// src/auto-rig/head.ts
|
|
2739
3378
|
function outerEdge(runs, axisX, side) {
|
|
2740
3379
|
if (runs.length === 0) return void 0;
|
|
@@ -3764,6 +4403,7 @@ function familyField(m, family) {
|
|
|
3764
4403
|
case "hair_back":
|
|
3765
4404
|
return hairField(m, m.hairBack, NOD.hairBack);
|
|
3766
4405
|
case "body":
|
|
4406
|
+
case "arm":
|
|
3767
4407
|
return () => [0, 0];
|
|
3768
4408
|
}
|
|
3769
4409
|
}
|
|
@@ -3863,7 +4503,7 @@ function renderedLander(turn, parts, grids, from = /* @__PURE__ */ new Map()) {
|
|
|
3863
4503
|
const family = roleSpec(role).family;
|
|
3864
4504
|
const part = parts.find((p) => p.id === role);
|
|
3865
4505
|
const g = grids.get(family);
|
|
3866
|
-
const warp = part?.warps?.find((w) => w.parameter ===
|
|
4506
|
+
const warp = part?.warps?.find((w) => w.parameter === import_format7.StandardParameter.AngleX);
|
|
3867
4507
|
if (part === void 0 || part.mesh === void 0 || !g && !warp) {
|
|
3868
4508
|
return x + field(role)(x, y, ax, 0)[0];
|
|
3869
4509
|
}
|
|
@@ -4572,177 +5212,6 @@ function buildTurn(frame, landmarks, q) {
|
|
|
4572
5212
|
};
|
|
4573
5213
|
}
|
|
4574
5214
|
|
|
4575
|
-
// src/auto-rig/drivers.ts
|
|
4576
|
-
var import_format5 = require("@ikijs/format");
|
|
4577
|
-
var SWAY_RANGE = 20;
|
|
4578
|
-
var SWAY_CURVE = 1.4;
|
|
4579
|
-
var HAIR_HANG = 0.35;
|
|
4580
|
-
var Z_STOPS = [-30, 0, 30];
|
|
4581
|
-
function localWarp(parameter, mesh, box, values, f) {
|
|
4582
|
-
const pts = meshPoints(mesh, box);
|
|
4583
|
-
const w = bw(box);
|
|
4584
|
-
const h = bh(box);
|
|
4585
|
-
return {
|
|
4586
|
-
parameter,
|
|
4587
|
-
keyforms: values.map((value) => ({
|
|
4588
|
-
value,
|
|
4589
|
-
offsets: pts.flatMap((p, i) => {
|
|
4590
|
-
const [dx, dy] = f(p, value, i);
|
|
4591
|
-
return [roundTo(dx / w, 1e-4), roundTo(dy / h, 1e-4)];
|
|
4592
|
-
})
|
|
4593
|
-
}))
|
|
4594
|
-
};
|
|
4595
|
-
}
|
|
4596
|
-
var rollOf = (v) => -v / 30 * ROLL_DEG * DEG;
|
|
4597
|
-
function motionReach(warps, mesh, box, extra = [0, 0]) {
|
|
4598
|
-
const out = {
|
|
4599
|
-
left: extra[0],
|
|
4600
|
-
right: extra[0],
|
|
4601
|
-
bottom: extra[1],
|
|
4602
|
-
top: extra[1]
|
|
4603
|
-
};
|
|
4604
|
-
if (mesh === void 0) return out;
|
|
4605
|
-
const pts = meshPoints(mesh, box);
|
|
4606
|
-
for (const warp of warps ?? []) {
|
|
4607
|
-
let l = 0;
|
|
4608
|
-
let r = 0;
|
|
4609
|
-
let b = 0;
|
|
4610
|
-
let t = 0;
|
|
4611
|
-
for (const k of warp.keyforms) {
|
|
4612
|
-
pts.forEach(([x, y], i) => {
|
|
4613
|
-
const px = x + k.offsets[2 * i] * bw(box);
|
|
4614
|
-
const py = y + k.offsets[2 * i + 1] * bh(box);
|
|
4615
|
-
l = Math.max(l, box.x0 - px);
|
|
4616
|
-
r = Math.max(r, px - box.x1);
|
|
4617
|
-
b = Math.max(b, box.y0 - py);
|
|
4618
|
-
t = Math.max(t, py - box.y1);
|
|
4619
|
-
});
|
|
4620
|
-
}
|
|
4621
|
-
out.left += l;
|
|
4622
|
-
out.right += r;
|
|
4623
|
-
out.bottom += b;
|
|
4624
|
-
out.top += t;
|
|
4625
|
-
}
|
|
4626
|
-
return out;
|
|
4627
|
-
}
|
|
4628
|
-
function blinkFold(param, mesh, box, travel, eye = box) {
|
|
4629
|
-
const yc = eye.y1 - travel * bh(eye);
|
|
4630
|
-
return localWarp(
|
|
4631
|
-
param,
|
|
4632
|
-
mesh,
|
|
4633
|
-
box,
|
|
4634
|
-
[0, 1],
|
|
4635
|
-
([, y], v) => v === 1 ? [0, 0] : [0, yc - y]
|
|
4636
|
-
);
|
|
4637
|
-
}
|
|
4638
|
-
function lashFold(param, mesh, lash, eye, travel) {
|
|
4639
|
-
const yc = eye.y1 - travel * bh(eye) - 0.04 * bh(eye);
|
|
4640
|
-
const c = cx(lash);
|
|
4641
|
-
const half = bw(lash) / 2;
|
|
4642
|
-
const sag = 0.3 * bh(lash);
|
|
4643
|
-
return localWarp(param, mesh, lash, [0, 1], ([x, y], v) => {
|
|
4644
|
-
if (v === 1) return [0, 0];
|
|
4645
|
-
const u = (x - c) / half;
|
|
4646
|
-
return [0, yc + (y - lash.y0) * 0.5 - sag * (1 - u * u) - y];
|
|
4647
|
-
});
|
|
4648
|
-
}
|
|
4649
|
-
function gazeBindings(irisW, share = 1) {
|
|
4650
|
-
const gx = AMPLITUDE.gazeX * irisW * share;
|
|
4651
|
-
const gy = AMPLITUDE.gazeY * irisW * share;
|
|
4652
|
-
return [
|
|
4653
|
-
{
|
|
4654
|
-
parameter: import_format5.StandardParameter.EyeballX,
|
|
4655
|
-
channel: "translateX",
|
|
4656
|
-
from: -round1(gx),
|
|
4657
|
-
to: round1(gx)
|
|
4658
|
-
},
|
|
4659
|
-
{
|
|
4660
|
-
parameter: import_format5.StandardParameter.EyeballY,
|
|
4661
|
-
channel: "translateY",
|
|
4662
|
-
from: -round1(gy),
|
|
4663
|
-
to: round1(gy)
|
|
4664
|
-
}
|
|
4665
|
-
];
|
|
4666
|
-
}
|
|
4667
|
-
function gazeReach(irisW, share = 1) {
|
|
4668
|
-
return [AMPLITUDE.gazeX * irisW * share, AMPLITUDE.gazeY * irisW * share];
|
|
4669
|
-
}
|
|
4670
|
-
function browReach(hh) {
|
|
4671
|
-
return AMPLITUDE.brow * hh;
|
|
4672
|
-
}
|
|
4673
|
-
function browBindings(side, reach) {
|
|
4674
|
-
const y = side === "L" ? import_format5.StandardParameter.BrowLeftY : import_format5.StandardParameter.BrowRightY;
|
|
4675
|
-
const a = side === "L" ? import_format5.StandardParameter.BrowLeftAngle : import_format5.StandardParameter.BrowRightAngle;
|
|
4676
|
-
return [
|
|
4677
|
-
{
|
|
4678
|
-
parameter: y,
|
|
4679
|
-
channel: "translateY",
|
|
4680
|
-
from: -round1(reach),
|
|
4681
|
-
to: round1(reach)
|
|
4682
|
-
},
|
|
4683
|
-
{ parameter: a, channel: "rotate", from: -BROW_TILT, to: BROW_TILT }
|
|
4684
|
-
];
|
|
4685
|
-
}
|
|
4686
|
-
var BROW_TILT = 15;
|
|
4687
|
-
function mouthForm(mesh, box) {
|
|
4688
|
-
const a = 0.3 * bh(box);
|
|
4689
|
-
const c = cx(box);
|
|
4690
|
-
const half = bw(box) / 2;
|
|
4691
|
-
return localWarp(import_format5.StandardParameter.MouthForm, mesh, box, [-1, 0, 1], ([x], v) => {
|
|
4692
|
-
if (v === 0) return [0, 0];
|
|
4693
|
-
const u = (x - c) / half;
|
|
4694
|
-
const widen = v > 0 ? 0.05 : 0.03;
|
|
4695
|
-
return [v * widen * (x - c), v * a * (u * u - 0.3)];
|
|
4696
|
-
});
|
|
4697
|
-
}
|
|
4698
|
-
function mouthOpenGrow(mesh, box) {
|
|
4699
|
-
const c = cx(box);
|
|
4700
|
-
return localWarp(
|
|
4701
|
-
import_format5.StandardParameter.MouthOpen,
|
|
4702
|
-
mesh,
|
|
4703
|
-
box,
|
|
4704
|
-
[0, 1],
|
|
4705
|
-
([x, y], v) => v === 1 ? [(AMPLITUDE.mouthOpenWidth - 1) * (x - c), 0] : [0, box.y1 + (y - box.y1) * 0.2 - y]
|
|
4706
|
-
);
|
|
4707
|
-
}
|
|
4708
|
-
function mouthWiden(mesh, box) {
|
|
4709
|
-
const c = cx(box);
|
|
4710
|
-
return localWarp(import_format5.StandardParameter.MouthOpen, mesh, box, [0, 1], ([x], v) => [
|
|
4711
|
-
v * (AMPLITUDE.mouthOpenWidth - 1) * (x - c),
|
|
4712
|
-
0
|
|
4713
|
-
]);
|
|
4714
|
-
}
|
|
4715
|
-
function hairSway(param, mesh, box, amp) {
|
|
4716
|
-
return localWarp(
|
|
4717
|
-
param,
|
|
4718
|
-
mesh,
|
|
4719
|
-
box,
|
|
4720
|
-
[-SWAY_RANGE, 0, SWAY_RANGE],
|
|
4721
|
-
([, y], v) => {
|
|
4722
|
-
const t = Math.max(0, (box.y1 - y) / bh(box));
|
|
4723
|
-
return [v / SWAY_RANGE * amp * t ** SWAY_CURVE, 0];
|
|
4724
|
-
}
|
|
4725
|
-
);
|
|
4726
|
-
}
|
|
4727
|
-
function tiltHang(mesh, box, pivot, weight) {
|
|
4728
|
-
return localWarp(import_format5.StandardParameter.AngleZ, mesh, box, Z_STOPS, ([x, y], v, i) => {
|
|
4729
|
-
const g = weight(y, i);
|
|
4730
|
-
if (g === 0 || v === 0) return [0, 0];
|
|
4731
|
-
const a = -g * rollOf(v);
|
|
4732
|
-
const px = x - pivot[0];
|
|
4733
|
-
const py = y - pivot[1];
|
|
4734
|
-
return [
|
|
4735
|
-
pivot[0] + px * Math.cos(a) - py * Math.sin(a) - x,
|
|
4736
|
-
pivot[1] + px * Math.sin(a) + py * Math.cos(a) - y
|
|
4737
|
-
];
|
|
4738
|
-
});
|
|
4739
|
-
}
|
|
4740
|
-
function hairHangWeight(pivotY, bottom) {
|
|
4741
|
-
if (bottom >= pivotY) return () => 0;
|
|
4742
|
-
return (y) => HAIR_HANG * smoothstep(pivotY, bottom, y);
|
|
4743
|
-
}
|
|
4744
|
-
var round1 = (v) => roundTo(v, 0.1);
|
|
4745
|
-
|
|
4746
5215
|
// src/auto-rig/generate.ts
|
|
4747
5216
|
var MESH_CELLS = {
|
|
4748
5217
|
face: { px: 28, min: 4, max: 24 },
|
|
@@ -4769,6 +5238,7 @@ var WARP_ID = {
|
|
|
4769
5238
|
mouth: "mouthWarp"
|
|
4770
5239
|
};
|
|
4771
5240
|
var isGridFamily = (f) => f in WARP_ID;
|
|
5241
|
+
var isArm = (spec) => spec.family === "arm";
|
|
4772
5242
|
var GRID_PAD = 6;
|
|
4773
5243
|
function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
4774
5244
|
checkLayers(layers, canvas);
|
|
@@ -4817,6 +5287,18 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
4817
5287
|
chinSlide: chinSlide(fit.model)
|
|
4818
5288
|
});
|
|
4819
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;
|
|
4820
5302
|
const parts = [];
|
|
4821
5303
|
const reach = /* @__PURE__ */ new Map();
|
|
4822
5304
|
let regionOf = [];
|
|
@@ -4824,6 +5306,12 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
4824
5306
|
for (const spec of ROLE_TABLE) {
|
|
4825
5307
|
const layer = byRole.get(spec.role);
|
|
4826
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
|
+
}
|
|
4827
5315
|
const built = buildPart(
|
|
4828
5316
|
spec,
|
|
4829
5317
|
boxOfLayer(layer),
|
|
@@ -4842,7 +5330,9 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
4842
5330
|
}
|
|
4843
5331
|
const grids = /* @__PURE__ */ new Map();
|
|
4844
5332
|
for (const family of Object.keys(WARP_ID)) {
|
|
4845
|
-
const members = parts.filter(
|
|
5333
|
+
const members = parts.filter(
|
|
5334
|
+
(p) => roleSpec(roleOfPart(p.id)).family === family
|
|
5335
|
+
);
|
|
4846
5336
|
if (members.length === 0) continue;
|
|
4847
5337
|
const area = unionBoxes(
|
|
4848
5338
|
members.map((p) => {
|
|
@@ -4864,13 +5354,19 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
4864
5354
|
});
|
|
4865
5355
|
}
|
|
4866
5356
|
for (const part of parts) {
|
|
4867
|
-
const family = roleSpec(part.id).family;
|
|
5357
|
+
const family = roleSpec(roleOfPart(part.id)).family;
|
|
4868
5358
|
if (isGridFamily(family)) {
|
|
4869
5359
|
part.deformer = WARP_ID[family];
|
|
4870
5360
|
continue;
|
|
4871
5361
|
}
|
|
4872
|
-
|
|
4873
|
-
|
|
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;
|
|
4874
5370
|
const own = familyField(turn, family);
|
|
4875
5371
|
const fieldOf = part.id === "face" ? plateFields(turn, regionOf, own) : () => own;
|
|
4876
5372
|
part.warps = [
|
|
@@ -4880,17 +5376,17 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
4880
5376
|
}
|
|
4881
5377
|
const parameters = declareParameters(new Set(byRole.keys()));
|
|
4882
5378
|
const model = {
|
|
4883
|
-
version:
|
|
5379
|
+
version: import_format8.IKI_FORMAT_VERSION,
|
|
4884
5380
|
name: "auto-rigged",
|
|
4885
5381
|
canvas: { width: canvas.width, height: canvas.height },
|
|
4886
5382
|
parameters,
|
|
4887
5383
|
parts,
|
|
4888
|
-
deformers: deformers(frame, grids,
|
|
5384
|
+
deformers: deformers(frame, grids, bodyWarp?.deformer, arms),
|
|
4889
5385
|
...has("hair_front") ? { physics: hairPhysics() } : {},
|
|
4890
5386
|
expressions: defaultExpressions(new Set(parameters.map((p) => p.id))),
|
|
4891
5387
|
motions: DEFAULT_MOTIONS
|
|
4892
5388
|
};
|
|
4893
|
-
const valid = (0,
|
|
5389
|
+
const valid = (0, import_format8.parseIkiModel)(model);
|
|
4894
5390
|
if (hasNose && options.onTurnSolved) {
|
|
4895
5391
|
options.onTurnSolved(
|
|
4896
5392
|
turnReport(
|
|
@@ -4905,14 +5401,14 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
4905
5401
|
function turnWarps(mesh, b, fieldOf) {
|
|
4906
5402
|
return [
|
|
4907
5403
|
localWarp(
|
|
4908
|
-
|
|
5404
|
+
import_format8.StandardParameter.AngleX,
|
|
4909
5405
|
mesh,
|
|
4910
5406
|
b,
|
|
4911
5407
|
X_STOPS,
|
|
4912
5408
|
([x, y], v, i) => v === 0 ? [0, 0] : [fieldOf(i)(x, y, v, 0)[0], 0]
|
|
4913
5409
|
),
|
|
4914
5410
|
localWarp(
|
|
4915
|
-
|
|
5411
|
+
import_format8.StandardParameter.AngleY,
|
|
4916
5412
|
mesh,
|
|
4917
5413
|
b,
|
|
4918
5414
|
Y_STOPS,
|
|
@@ -4960,16 +5456,22 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
4960
5456
|
}
|
|
4961
5457
|
if (mesh !== void 0) part.mesh = mesh;
|
|
4962
5458
|
const side = spec.role.endsWith("_L") ? "L" : "R";
|
|
4963
|
-
const eyeOpen = side === "L" ?
|
|
5459
|
+
const eyeOpen = side === "L" ? import_format8.StandardParameter.EyeOpenLeft : import_format8.StandardParameter.EyeOpenRight;
|
|
4964
5460
|
const hh = frame.hh;
|
|
4965
5461
|
switch (spec.role) {
|
|
4966
5462
|
case "face":
|
|
4967
5463
|
if (region?.some((r) => r === "neck")) {
|
|
4968
5464
|
const isNeck = region.map((r) => r === "neck");
|
|
4969
5465
|
warps.push(
|
|
4970
|
-
tiltHang(
|
|
5466
|
+
tiltHang(
|
|
5467
|
+
mesh,
|
|
5468
|
+
b,
|
|
5469
|
+
pivot,
|
|
5470
|
+
(_y, i) => isNeck[i] ? 1 : 0,
|
|
5471
|
+
headOwnRoll(byRole.has("body"))
|
|
5472
|
+
),
|
|
4971
5473
|
localWarp(
|
|
4972
|
-
|
|
5474
|
+
import_format8.StandardParameter.Breath,
|
|
4973
5475
|
mesh,
|
|
4974
5476
|
b,
|
|
4975
5477
|
[0, 1],
|
|
@@ -5016,8 +5518,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5016
5518
|
if (byRole.has("mouth_open")) {
|
|
5017
5519
|
warps.push(mouthWiden(mesh, b));
|
|
5018
5520
|
bindings.push(
|
|
5019
|
-
{ parameter:
|
|
5020
|
-
{ 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 }
|
|
5021
5523
|
);
|
|
5022
5524
|
extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
|
|
5023
5525
|
} else {
|
|
@@ -5027,19 +5529,19 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5027
5529
|
);
|
|
5028
5530
|
bindings.push(
|
|
5029
5531
|
{
|
|
5030
|
-
parameter:
|
|
5532
|
+
parameter: import_format8.StandardParameter.MouthOpen,
|
|
5031
5533
|
channel: "scaleY",
|
|
5032
5534
|
from: 0,
|
|
5033
5535
|
to: roundTo(grow, 0.01)
|
|
5034
5536
|
},
|
|
5035
5537
|
{
|
|
5036
|
-
parameter:
|
|
5538
|
+
parameter: import_format8.StandardParameter.MouthOpen,
|
|
5037
5539
|
channel: "scaleX",
|
|
5038
5540
|
from: 0,
|
|
5039
5541
|
to: roundTo(AMPLITUDE.mouthOpenWidth - 1, 0.01)
|
|
5040
5542
|
},
|
|
5041
5543
|
{
|
|
5042
|
-
parameter:
|
|
5544
|
+
parameter: import_format8.StandardParameter.MouthOpen,
|
|
5043
5545
|
channel: "translateY",
|
|
5044
5546
|
from: 0,
|
|
5045
5547
|
to: -roundTo(grow * bh(b) / 2, 0.01)
|
|
@@ -5051,7 +5553,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5051
5553
|
case "mouth_open":
|
|
5052
5554
|
warps.push(mouthForm(mesh, b), mouthOpenGrow(mesh, b));
|
|
5053
5555
|
bindings.push({
|
|
5054
|
-
parameter:
|
|
5556
|
+
parameter: import_format8.StandardParameter.MouthOpen,
|
|
5055
5557
|
channel: "opacity",
|
|
5056
5558
|
from: 0,
|
|
5057
5559
|
to: 1
|
|
@@ -5061,7 +5563,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5061
5563
|
case "blush_L":
|
|
5062
5564
|
case "blush_R":
|
|
5063
5565
|
bindings.push({
|
|
5064
|
-
parameter:
|
|
5566
|
+
parameter: import_format8.StandardParameter.Cheek,
|
|
5065
5567
|
channel: "opacity",
|
|
5066
5568
|
from: AMPLITUDE.blushRest,
|
|
5067
5569
|
to: 1
|
|
@@ -5072,8 +5574,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5072
5574
|
if (byRole.has("hair_front")) {
|
|
5073
5575
|
const amp = style.sway * AMPLITUDE.sway * hh;
|
|
5074
5576
|
warps.push(
|
|
5075
|
-
hairSway(
|
|
5076
|
-
hairSway(
|
|
5577
|
+
hairSway(import_format8.StandardParameter.HairSwayX, mesh, b, amp),
|
|
5578
|
+
hairSway(import_format8.StandardParameter.HairSwayZ, mesh, b, amp)
|
|
5077
5579
|
);
|
|
5078
5580
|
}
|
|
5079
5581
|
warps.push(tiltHang(mesh, b, pivot, hairHangWeight(frame.chinY, b.y0)));
|
|
@@ -5146,33 +5648,24 @@ function meshScale(role, frame) {
|
|
|
5146
5648
|
return Infinity;
|
|
5147
5649
|
}
|
|
5148
5650
|
}
|
|
5149
|
-
function deformers(frame, grids,
|
|
5651
|
+
function deformers(frame, grids, bodyWarp, arms) {
|
|
5150
5652
|
const out = [];
|
|
5151
|
-
if (
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
bindings: [
|
|
5156
|
-
{
|
|
5157
|
-
parameter: import_format6.StandardParameter.Breath,
|
|
5158
|
-
channel: "translateY",
|
|
5159
|
-
from: 0,
|
|
5160
|
-
to: roundTo(AMPLITUDE.breathBody * frame.hh, 0.01)
|
|
5161
|
-
}
|
|
5162
|
-
]
|
|
5163
|
-
});
|
|
5164
|
-
}
|
|
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);
|
|
5165
5657
|
out.push({
|
|
5166
5658
|
id: "headDeformer",
|
|
5659
|
+
...hasBody ? { parent: BODY_WARP_ID } : {},
|
|
5167
5660
|
pivot: { x: roundTo(frame.axisX, 0.01), y: roundTo(frame.chinY, 0.01) },
|
|
5168
5661
|
bindings: [
|
|
5169
5662
|
// AngleZ is clockwise-positive (Live2D's); a rotation is CCW-positive.
|
|
5170
|
-
{ parameter:
|
|
5663
|
+
{ parameter: import_format8.StandardParameter.AngleZ, channel: "rotate", from: roll, to: -roll },
|
|
5171
5664
|
{
|
|
5172
|
-
parameter:
|
|
5665
|
+
parameter: import_format8.StandardParameter.Breath,
|
|
5173
5666
|
channel: "translateY",
|
|
5174
5667
|
from: 0,
|
|
5175
|
-
to: roundTo(
|
|
5668
|
+
to: roundTo(headOwnBreath(hasBody) * frame.hh, 0.01)
|
|
5176
5669
|
}
|
|
5177
5670
|
]
|
|
5178
5671
|
});
|
|
@@ -5187,8 +5680,8 @@ function deformers(frame, grids, body) {
|
|
|
5187
5680
|
points: g.lattice.points
|
|
5188
5681
|
},
|
|
5189
5682
|
warp2d: {
|
|
5190
|
-
parameter:
|
|
5191
|
-
parameterY:
|
|
5683
|
+
parameter: import_format8.StandardParameter.AngleX,
|
|
5684
|
+
parameterY: import_format8.StandardParameter.AngleY,
|
|
5192
5685
|
valuesX: X_STOPS,
|
|
5193
5686
|
valuesY: Y_STOPS,
|
|
5194
5687
|
keyforms2d: g.keyforms.map((offsets) => ({ offsets }))
|
|
@@ -5201,16 +5694,16 @@ function hairPhysics() {
|
|
|
5201
5694
|
return [
|
|
5202
5695
|
{
|
|
5203
5696
|
id: "hairSway",
|
|
5204
|
-
input: { parameter:
|
|
5205
|
-
output: { parameter:
|
|
5697
|
+
input: { parameter: import_format8.StandardParameter.AngleX, weight: 1 },
|
|
5698
|
+
output: { parameter: import_format8.StandardParameter.HairSwayX, scale: 5 },
|
|
5206
5699
|
mass: 1,
|
|
5207
5700
|
stiffness: 30,
|
|
5208
5701
|
damping: 3
|
|
5209
5702
|
},
|
|
5210
5703
|
{
|
|
5211
5704
|
id: "hairTilt",
|
|
5212
|
-
input: { parameter:
|
|
5213
|
-
output: { parameter:
|
|
5705
|
+
input: { parameter: import_format8.StandardParameter.AngleZ, weight: 1 },
|
|
5706
|
+
output: { parameter: import_format8.StandardParameter.HairSwayZ, scale: 8 },
|
|
5214
5707
|
mass: 1,
|
|
5215
5708
|
stiffness: 25,
|
|
5216
5709
|
damping: 2.5
|
|
@@ -5220,32 +5713,46 @@ function hairPhysics() {
|
|
|
5220
5713
|
function declareParameters(roles) {
|
|
5221
5714
|
const out = [];
|
|
5222
5715
|
const add = (id, name, min, max, def) => out.push({ id, name, min, max, default: def });
|
|
5223
|
-
add(
|
|
5224
|
-
add(
|
|
5225
|
-
add(
|
|
5226
|
-
|
|
5227
|
-
|
|
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);
|
|
5228
5735
|
if ([...roles].some((r) => /^(iris|pupil|highlight)_/.test(r))) {
|
|
5229
|
-
add(
|
|
5230
|
-
add(
|
|
5736
|
+
add(import_format8.StandardParameter.EyeballX, "Gaze X", -1, 1, 0);
|
|
5737
|
+
add(import_format8.StandardParameter.EyeballY, "Gaze Y", -1, 1, 0);
|
|
5231
5738
|
}
|
|
5232
|
-
add(
|
|
5233
|
-
add(
|
|
5739
|
+
add(import_format8.StandardParameter.MouthOpen, "Mouth Open", 0, 1, 0);
|
|
5740
|
+
add(import_format8.StandardParameter.MouthForm, "Mouth Form", -1, 1, 0);
|
|
5234
5741
|
if (roles.has("brow_L")) {
|
|
5235
|
-
add(
|
|
5236
|
-
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);
|
|
5237
5744
|
}
|
|
5238
5745
|
if (roles.has("brow_R")) {
|
|
5239
|
-
add(
|
|
5240
|
-
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);
|
|
5241
5748
|
}
|
|
5242
5749
|
if (roles.has("blush_L") || roles.has("blush_R")) {
|
|
5243
|
-
add(
|
|
5750
|
+
add(import_format8.StandardParameter.Cheek, "Cheek", 0, 1, 0);
|
|
5244
5751
|
}
|
|
5245
|
-
add(
|
|
5752
|
+
add(import_format8.StandardParameter.Breath, "Breath", 0, 1, 0);
|
|
5246
5753
|
if (roles.has("hair_front")) {
|
|
5247
|
-
add(
|
|
5248
|
-
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);
|
|
5249
5756
|
}
|
|
5250
5757
|
return out;
|
|
5251
5758
|
}
|
|
@@ -5264,6 +5771,7 @@ function declareParameters(roles) {
|
|
|
5264
5771
|
DeletePhysicsRig,
|
|
5265
5772
|
EditorDocument,
|
|
5266
5773
|
HEAD_BAND,
|
|
5774
|
+
LayerGeometryError,
|
|
5267
5775
|
SPECK_CORE_FRACTION,
|
|
5268
5776
|
SetDeformerBindings,
|
|
5269
5777
|
SetDeformerParent,
|
|
@@ -5298,6 +5806,7 @@ function declareParameters(roles) {
|
|
|
5298
5806
|
isSpeckCore,
|
|
5299
5807
|
packAtlas,
|
|
5300
5808
|
parseLayerRoles,
|
|
5809
|
+
partIdsOfRole,
|
|
5301
5810
|
upsertGridKeyform,
|
|
5302
5811
|
uvRectFor,
|
|
5303
5812
|
validateDeformerDelete,
|