@ikijs/editor 0.30.0 → 0.31.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 +25 -14
- package/dist/index.d.mts +161 -4
- package/dist/index.d.ts +161 -4
- package/dist/index.js +885 -46
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +882 -46
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -26,7 +26,9 @@ __export(index_exports, {
|
|
|
26
26
|
AddDeformer: () => AddDeformer,
|
|
27
27
|
AddPart: () => AddPart,
|
|
28
28
|
AddPhysicsRig: () => AddPhysicsRig,
|
|
29
|
+
BakeError: () => BakeError,
|
|
29
30
|
CaptureGridKeyform: () => CaptureGridKeyform,
|
|
31
|
+
CustomPartError: () => CustomPartError,
|
|
30
32
|
DEFAULT_TURN_TARGETS: () => DEFAULT_TURN_TARGETS,
|
|
31
33
|
DeleteDeformer: () => DeleteDeformer,
|
|
32
34
|
DeletePart: () => DeletePart,
|
|
@@ -82,6 +84,7 @@ __export(index_exports, {
|
|
|
82
84
|
packAtlas: () => packAtlas,
|
|
83
85
|
parseLayerRoles: () => parseLayerRoles,
|
|
84
86
|
partIdsOfRole: () => partIdsOfRole,
|
|
87
|
+
rigidResidual: () => rigidResidual,
|
|
85
88
|
styleRemovedMessage: () => styleRemovedMessage,
|
|
86
89
|
upsertGridKeyform: () => upsertGridKeyform,
|
|
87
90
|
uvRectFor: () => uvRectFor,
|
|
@@ -2203,6 +2206,18 @@ var PatchError = class extends Error {
|
|
|
2203
2206
|
this.name = "PatchError";
|
|
2204
2207
|
}
|
|
2205
2208
|
};
|
|
2209
|
+
var CustomPartError = class extends Error {
|
|
2210
|
+
constructor(message) {
|
|
2211
|
+
super(message);
|
|
2212
|
+
this.name = "CustomPartError";
|
|
2213
|
+
}
|
|
2214
|
+
};
|
|
2215
|
+
var BakeError = class extends Error {
|
|
2216
|
+
constructor(message) {
|
|
2217
|
+
super(message);
|
|
2218
|
+
this.name = "BakeError";
|
|
2219
|
+
}
|
|
2220
|
+
};
|
|
2206
2221
|
|
|
2207
2222
|
// src/auto-rig/layout.ts
|
|
2208
2223
|
function boxOfLayer(layer) {
|
|
@@ -2309,7 +2324,7 @@ var import_format3 = require("@ikijs/format");
|
|
|
2309
2324
|
var SWAY_RANGE = 20;
|
|
2310
2325
|
var SWAY_CURVE = 1.4;
|
|
2311
2326
|
var HAIR_HANG = 0.35;
|
|
2312
|
-
var
|
|
2327
|
+
var LOCK_HANG = 0.45;
|
|
2313
2328
|
var Z_STOPS = [-30, 0, 30];
|
|
2314
2329
|
function localWarp(parameter, mesh, box, values, f) {
|
|
2315
2330
|
const pts = meshPoints(mesh, box);
|
|
@@ -2490,8 +2505,8 @@ function hairHangWeight(pivotY, bottom) {
|
|
|
2490
2505
|
if (bottom >= pivotY) return () => 0;
|
|
2491
2506
|
return (y) => HAIR_HANG * smoothstep(pivotY, bottom, y);
|
|
2492
2507
|
}
|
|
2493
|
-
function lockHangWeight(
|
|
2494
|
-
return
|
|
2508
|
+
function lockHangWeight(free) {
|
|
2509
|
+
return (y) => LOCK_HANG * free(y);
|
|
2495
2510
|
}
|
|
2496
2511
|
var round1 = (v) => roundTo(v, 0.1);
|
|
2497
2512
|
|
|
@@ -2607,10 +2622,10 @@ function checkSidesHaveFront(roles) {
|
|
|
2607
2622
|
function checkMouth(roles) {
|
|
2608
2623
|
const has = new Set(roles);
|
|
2609
2624
|
const present = LIP_ROLES.filter((r) => has.has(r));
|
|
2610
|
-
const
|
|
2611
|
-
if (
|
|
2625
|
+
const inside2 = LIP_INSIDE_ROLES.filter((r) => has.has(r));
|
|
2626
|
+
if (inside2.length > 0 && present.length === 0) {
|
|
2612
2627
|
throw new Error(
|
|
2613
|
-
`auto-rig: ${
|
|
2628
|
+
`auto-rig: ${inside2[0]} needs the lip set ${LIP_ROLES.join(", ")} (it is cut from the interior and clipped to it)`
|
|
2614
2629
|
);
|
|
2615
2630
|
}
|
|
2616
2631
|
if (present.length === 0) {
|
|
@@ -2830,10 +2845,10 @@ function mouthFold(role, opening) {
|
|
|
2830
2845
|
const stackThin = (c, y) => y > c.Tu ? c.Tu + UPPER_THIN * (y - c.Tu) : y;
|
|
2831
2846
|
return (col, y, v) => {
|
|
2832
2847
|
const k = 1 - v;
|
|
2833
|
-
const
|
|
2848
|
+
const inside2 = col >= x0 && col <= x1;
|
|
2834
2849
|
switch (role) {
|
|
2835
2850
|
case "lip_lower": {
|
|
2836
|
-
if (!
|
|
2851
|
+
if (!inside2) return [0, 0];
|
|
2837
2852
|
const c = opening.at(col);
|
|
2838
2853
|
return [0, (c.seam + c.overlap - c.Bl) * k];
|
|
2839
2854
|
}
|
|
@@ -2857,9 +2872,9 @@ function mouthFold(role, opening) {
|
|
|
2857
2872
|
line = opening.line(c += col < x0 ? 1 : -1);
|
|
2858
2873
|
const thin = line.bottom + UPPER_THIN * (y - line.bottom);
|
|
2859
2874
|
const mid = line.bottom + UPPER_THIN * (line.top - line.bottom) / 2;
|
|
2860
|
-
const travel =
|
|
2875
|
+
const travel = inside2 ? opening.at(col).seam - line.bottom : 0;
|
|
2861
2876
|
const shut = mid + line.squash * (thin - mid) + travel;
|
|
2862
|
-
const pull =
|
|
2877
|
+
const pull = inside2 ? 0 : (1 - FLICK_PULL) * ((col < x0 ? x0 : x1 + 1) - col);
|
|
2863
2878
|
return [pull * k, thin - y + (shut - thin) * k];
|
|
2864
2879
|
}
|
|
2865
2880
|
}
|
|
@@ -4192,14 +4207,14 @@ function cuesAt(ctx, land2, ax) {
|
|
|
4192
4207
|
const l0 = land2(L.role, L.lo, L.y, ax);
|
|
4193
4208
|
const l1 = land2(L.role, L.hi, L.y, ax);
|
|
4194
4209
|
const r0 = land2(R.role, R.lo, R.y, ax);
|
|
4195
|
-
const
|
|
4210
|
+
const r12 = land2(R.role, R.hi, R.y, ax);
|
|
4196
4211
|
const wl = l1 - l0;
|
|
4197
|
-
const wr =
|
|
4212
|
+
const wr = r12 - r0;
|
|
4198
4213
|
const wl0 = L.hi - L.lo;
|
|
4199
4214
|
const wr0 = R.hi - R.lo;
|
|
4200
4215
|
const ratio = ax < 0 ? wl / wr / (wl0 / wr0) : wr / wl / (wr0 / wl0);
|
|
4201
4216
|
const pc0 = (L.lo + L.hi + R.lo + R.hi) / 4;
|
|
4202
|
-
const pc = (l0 + l1 + r0 +
|
|
4217
|
+
const pc = (l0 + l1 + r0 + r12) / 4;
|
|
4203
4218
|
const y = ctx.frame.eyeY;
|
|
4204
4219
|
const hl0 = Math.min(...ctx.candidates.left.map((c) => c.x));
|
|
4205
4220
|
const hr0 = Math.max(...ctx.candidates.right.map((c) => c.x));
|
|
@@ -4833,7 +4848,7 @@ function blendStops(values, offsets, v) {
|
|
|
4833
4848
|
const t = (x - values[i]) / (values[i + 1] - values[i]);
|
|
4834
4849
|
return offsets[i].map((o, k) => o + (offsets[i + 1][k] - o) * t);
|
|
4835
4850
|
}
|
|
4836
|
-
function
|
|
4851
|
+
function bindToRest(l, x, y) {
|
|
4837
4852
|
const { cols, rows, points } = l;
|
|
4838
4853
|
const stride = cols + 1;
|
|
4839
4854
|
let col = cols - 1;
|
|
@@ -4854,8 +4869,17 @@ function land(l, offsets, x, y) {
|
|
|
4854
4869
|
const x1 = points[(col + 1) * 2];
|
|
4855
4870
|
const yT = points[row * stride * 2 + 1];
|
|
4856
4871
|
const yB = points[(row + 1) * stride * 2 + 1];
|
|
4857
|
-
|
|
4858
|
-
|
|
4872
|
+
return {
|
|
4873
|
+
row,
|
|
4874
|
+
col,
|
|
4875
|
+
s: clamp((x - x0) / (x1 - x0), 0, 1),
|
|
4876
|
+
t: clamp((yT - y) / (yT - yB), 0, 1)
|
|
4877
|
+
};
|
|
4878
|
+
}
|
|
4879
|
+
function land(l, offsets, x, y) {
|
|
4880
|
+
const { cols, points } = l;
|
|
4881
|
+
const stride = cols + 1;
|
|
4882
|
+
const { row, col, s, t } = bindToRest(l, x, y);
|
|
4859
4883
|
const at = (r, c) => {
|
|
4860
4884
|
const k = (r * stride + c) * 2;
|
|
4861
4885
|
return [points[k] + offsets[k], points[k + 1] + offsets[k + 1]];
|
|
@@ -5195,7 +5219,7 @@ function armGeometry(layer, bodyAxisX) {
|
|
|
5195
5219
|
b.x1 - elbow.x,
|
|
5196
5220
|
b.y1 - elbow.y
|
|
5197
5221
|
);
|
|
5198
|
-
const
|
|
5222
|
+
const inside2 = (x, y) => {
|
|
5199
5223
|
const k = Math.floor(y) - bbox.y;
|
|
5200
5224
|
if (k < 0 || k >= bbox.h) return false;
|
|
5201
5225
|
const [rs, re] = span3(k);
|
|
@@ -5204,7 +5228,7 @@ function armGeometry(layer, bodyAxisX) {
|
|
|
5204
5228
|
const capRays = Array.from({ length: CAP_SEGMENTS + 1 }, (_, i) => {
|
|
5205
5229
|
const a = Math.PI * i / CAP_SEGMENTS;
|
|
5206
5230
|
let t = 1;
|
|
5207
|
-
while (t <= capRadius + CAP_INSET &&
|
|
5231
|
+
while (t <= capRadius + CAP_INSET && inside2(
|
|
5208
5232
|
elbow.x + canvasW / 2 + t * Math.cos(a),
|
|
5209
5233
|
canvasH / 2 - elbow.y - t * Math.sin(a)
|
|
5210
5234
|
)) {
|
|
@@ -5404,6 +5428,675 @@ function forearmPoseDeformer(role, arm) {
|
|
|
5404
5428
|
};
|
|
5405
5429
|
}
|
|
5406
5430
|
|
|
5431
|
+
// src/auto-rig/chain.ts
|
|
5432
|
+
function multiply(a, b) {
|
|
5433
|
+
return [
|
|
5434
|
+
a[0] * b[0] + a[2] * b[1],
|
|
5435
|
+
a[1] * b[0] + a[3] * b[1],
|
|
5436
|
+
a[0] * b[2] + a[2] * b[3],
|
|
5437
|
+
a[1] * b[2] + a[3] * b[3],
|
|
5438
|
+
a[0] * b[4] + a[2] * b[5] + a[4],
|
|
5439
|
+
a[1] * b[4] + a[3] * b[5] + a[5]
|
|
5440
|
+
];
|
|
5441
|
+
}
|
|
5442
|
+
function valueOf(parameters, values, id) {
|
|
5443
|
+
const p = parameters.find((q) => q.id === id);
|
|
5444
|
+
if (p === void 0) return 0;
|
|
5445
|
+
const v = values[id] ?? p.default;
|
|
5446
|
+
return Math.min(p.max, Math.max(p.min, v));
|
|
5447
|
+
}
|
|
5448
|
+
function ownMatrix(d, parameters, values) {
|
|
5449
|
+
let x = d.transform?.x ?? 0;
|
|
5450
|
+
let y = d.transform?.y ?? 0;
|
|
5451
|
+
let rotation = d.transform?.rotation ?? 0;
|
|
5452
|
+
let sx = d.transform?.scaleX ?? 1;
|
|
5453
|
+
let sy = d.transform?.scaleY ?? 1;
|
|
5454
|
+
for (const b of d.bindings ?? []) {
|
|
5455
|
+
const p = parameters.find((q) => q.id === b.parameter);
|
|
5456
|
+
const t = p === void 0 || p.max === p.min ? 0 : (valueOf(parameters, values, b.parameter) - p.min) / (p.max - p.min);
|
|
5457
|
+
const v = b.from + (b.to - b.from) * t;
|
|
5458
|
+
if (b.channel === "translateX") x += v;
|
|
5459
|
+
else if (b.channel === "translateY") y += v;
|
|
5460
|
+
else if (b.channel === "rotate") rotation += v;
|
|
5461
|
+
else if (b.channel === "scaleX") sx += v;
|
|
5462
|
+
else if (b.channel === "scaleY") sy += v;
|
|
5463
|
+
}
|
|
5464
|
+
const r = rotation * Math.PI / 180;
|
|
5465
|
+
const c = Math.cos(r);
|
|
5466
|
+
const s = Math.sin(r);
|
|
5467
|
+
const trs = [c * sx, s * sx, -s * sy, c * sy, x, y];
|
|
5468
|
+
return multiply(multiply([1, 0, 0, 1, d.pivot.x, d.pivot.y], trs), [
|
|
5469
|
+
1,
|
|
5470
|
+
0,
|
|
5471
|
+
0,
|
|
5472
|
+
1,
|
|
5473
|
+
-d.pivot.x,
|
|
5474
|
+
-d.pivot.y
|
|
5475
|
+
]);
|
|
5476
|
+
}
|
|
5477
|
+
function localGrid(w, parameters, values) {
|
|
5478
|
+
const points = [...w.grid.points];
|
|
5479
|
+
for (const warp of w.warps ?? []) {
|
|
5480
|
+
const offsets = blendStops(
|
|
5481
|
+
warp.keyforms.map((k) => k.value),
|
|
5482
|
+
warp.keyforms.map((k) => k.offsets),
|
|
5483
|
+
valueOf(parameters, values, warp.parameter)
|
|
5484
|
+
);
|
|
5485
|
+
for (let i = 0; i < points.length; i++) points[i] += offsets[i];
|
|
5486
|
+
}
|
|
5487
|
+
return points;
|
|
5488
|
+
}
|
|
5489
|
+
function riddenFrame(pivot, w, q) {
|
|
5490
|
+
const { cols, points: rest } = w.grid;
|
|
5491
|
+
const stride = cols + 1;
|
|
5492
|
+
const { row, col, s, t } = bindToRest(w.grid, pivot.x, pivot.y);
|
|
5493
|
+
const xl = rest[col * 2];
|
|
5494
|
+
const xr = rest[(col + 1) * 2];
|
|
5495
|
+
const yt = rest[row * stride * 2 + 1];
|
|
5496
|
+
const yb = rest[(row + 1) * stride * 2 + 1];
|
|
5497
|
+
const i00 = (row * stride + col) * 2;
|
|
5498
|
+
const i10 = (row * stride + col + 1) * 2;
|
|
5499
|
+
const i01 = ((row + 1) * stride + col) * 2;
|
|
5500
|
+
const i11 = ((row + 1) * stride + col + 1) * 2;
|
|
5501
|
+
const lerp = (a, b, k) => a + (b - a) * k;
|
|
5502
|
+
const topX = lerp(q[i00], q[i10], s);
|
|
5503
|
+
const topY = lerp(q[i00 + 1], q[i10 + 1], s);
|
|
5504
|
+
const botX = lerp(q[i01], q[i11], s);
|
|
5505
|
+
const botY = lerp(q[i01 + 1], q[i11 + 1], s);
|
|
5506
|
+
const px = lerp(topX, botX, t);
|
|
5507
|
+
const py = lerp(topY, botY, t);
|
|
5508
|
+
const width = xr - xl;
|
|
5509
|
+
const height = yt - yb;
|
|
5510
|
+
const exX = lerp(q[i10] - q[i00], q[i11] - q[i01], t) / width;
|
|
5511
|
+
const exY = lerp(q[i10 + 1] - q[i00 + 1], q[i11 + 1] - q[i01 + 1], t) / width;
|
|
5512
|
+
const eyX = -lerp(q[i01] - q[i00], q[i11] - q[i10], s) / height;
|
|
5513
|
+
const eyY = -lerp(q[i01 + 1] - q[i00 + 1], q[i11 + 1] - q[i10 + 1], s) / height;
|
|
5514
|
+
const theta = Math.atan2(exY - eyX, exX + eyY);
|
|
5515
|
+
const c = Math.cos(theta);
|
|
5516
|
+
const sn = Math.sin(theta);
|
|
5517
|
+
return [
|
|
5518
|
+
c,
|
|
5519
|
+
sn,
|
|
5520
|
+
-sn,
|
|
5521
|
+
c,
|
|
5522
|
+
px - (c * pivot.x - sn * pivot.y),
|
|
5523
|
+
py - (sn * pivot.x + c * pivot.y)
|
|
5524
|
+
];
|
|
5525
|
+
}
|
|
5526
|
+
function matrixWorldAt(deformers2, parameters, id, values = {}) {
|
|
5527
|
+
for (const key of Object.keys(values)) {
|
|
5528
|
+
if (!parameters.some((q) => q.id === key)) {
|
|
5529
|
+
throw new Error(`chain: unknown parameter "${key}"`);
|
|
5530
|
+
}
|
|
5531
|
+
}
|
|
5532
|
+
const d = deformers2.find((x) => x.id === id);
|
|
5533
|
+
if (d === void 0) throw new Error(`chain: no deformer "${id}"`);
|
|
5534
|
+
if (d.kind === "warp") {
|
|
5535
|
+
throw new Error(`chain: "${id}" is a warp deformer, not a matrix`);
|
|
5536
|
+
}
|
|
5537
|
+
const own = ownMatrix(d, parameters, values);
|
|
5538
|
+
if (d.parent === void 0) return own;
|
|
5539
|
+
const parent = deformers2.find((x) => x.id === d.parent);
|
|
5540
|
+
if (parent === void 0) {
|
|
5541
|
+
throw new Error(`chain: "${id}" has an unknown parent "${d.parent}"`);
|
|
5542
|
+
}
|
|
5543
|
+
if (parent.kind !== "warp") {
|
|
5544
|
+
throw new Error(
|
|
5545
|
+
`chain: "${id}" hangs from the matrix deformer "${parent.id}"; only a root warp is supported`
|
|
5546
|
+
);
|
|
5547
|
+
}
|
|
5548
|
+
if (parent.parent !== void 0) {
|
|
5549
|
+
throw new Error(
|
|
5550
|
+
`chain: "${id}" hangs from warp "${parent.id}", which has a parent "${parent.parent}"; only a root warp is supported`
|
|
5551
|
+
);
|
|
5552
|
+
}
|
|
5553
|
+
if (parent.warp2d !== void 0) {
|
|
5554
|
+
throw new Error(
|
|
5555
|
+
`chain: "${id}" hangs from warp "${parent.id}", which carries a warp2d; only 1D warps are supported`
|
|
5556
|
+
);
|
|
5557
|
+
}
|
|
5558
|
+
return multiply(
|
|
5559
|
+
riddenFrame(d.pivot, parent, localGrid(parent, parameters, values)),
|
|
5560
|
+
own
|
|
5561
|
+
);
|
|
5562
|
+
}
|
|
5563
|
+
|
|
5564
|
+
// src/auto-rig/bakes.ts
|
|
5565
|
+
var TARGET_KEYS = [
|
|
5566
|
+
"id",
|
|
5567
|
+
"param",
|
|
5568
|
+
"parts",
|
|
5569
|
+
"box",
|
|
5570
|
+
"grid",
|
|
5571
|
+
"stops",
|
|
5572
|
+
"mirror",
|
|
5573
|
+
"why"
|
|
5574
|
+
];
|
|
5575
|
+
var BOX_KEYS = ["x", "y", "w", "h"];
|
|
5576
|
+
var MAX_VERTICES = 65536;
|
|
5577
|
+
var CELL_PX = [4, 400];
|
|
5578
|
+
var MAX_MESH_COLS = 64;
|
|
5579
|
+
var MAX_MESH_ROWS = 400;
|
|
5580
|
+
var DEFAULT_BOX_MARGIN = 2;
|
|
5581
|
+
var SAME_STOP = 1e-9;
|
|
5582
|
+
var PLAIN_DECIMAL = /^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/;
|
|
5583
|
+
var finite = (v) => typeof v === "number" && Number.isFinite(v);
|
|
5584
|
+
var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
5585
|
+
var show = (v) => typeof v === "number" ? String(v) : JSON.stringify(v) ?? String(v);
|
|
5586
|
+
function mirrorLattice(lattice2, cols, rows) {
|
|
5587
|
+
const out = new Array(lattice2.length);
|
|
5588
|
+
for (let r = 0; r <= rows; r++) {
|
|
5589
|
+
for (let c = 0; c <= cols; c++) {
|
|
5590
|
+
const [dx, dy] = lattice2[r * (cols + 1) + (cols - c)];
|
|
5591
|
+
out[r * (cols + 1) + c] = [-dx, dy];
|
|
5592
|
+
}
|
|
5593
|
+
}
|
|
5594
|
+
return out;
|
|
5595
|
+
}
|
|
5596
|
+
var r1 = (v) => Math.round(v * 10) / 10;
|
|
5597
|
+
var findStop = (stops, v) => [...stops.keys()].find((k) => Math.abs(k - v) <= SAME_STOP);
|
|
5598
|
+
function checkGrid(at, grid) {
|
|
5599
|
+
if (!isObject(grid)) {
|
|
5600
|
+
throw new BakeError(`${at}.grid must be { cols, rows }, got ${show(grid)}`);
|
|
5601
|
+
}
|
|
5602
|
+
checkedKeys(`${at}.grid`, grid, ["cols", "rows"], BakeError);
|
|
5603
|
+
const out = { cols: 0, rows: 0 };
|
|
5604
|
+
for (const key of ["cols", "rows"]) {
|
|
5605
|
+
const v = checkedNumber(
|
|
5606
|
+
`${at}.grid.${key}`,
|
|
5607
|
+
grid[key],
|
|
5608
|
+
1,
|
|
5609
|
+
32,
|
|
5610
|
+
false,
|
|
5611
|
+
BakeError
|
|
5612
|
+
);
|
|
5613
|
+
if (!Number.isInteger(v)) {
|
|
5614
|
+
throw new BakeError(`${at}.grid.${key} must be an integer, got ${v}`);
|
|
5615
|
+
}
|
|
5616
|
+
out[key] = v;
|
|
5617
|
+
}
|
|
5618
|
+
return out;
|
|
5619
|
+
}
|
|
5620
|
+
function checkBox(at, box, partBoxes) {
|
|
5621
|
+
if (box === void 0) {
|
|
5622
|
+
const x0 = Math.min(...partBoxes.map((b) => b.x)) - DEFAULT_BOX_MARGIN;
|
|
5623
|
+
const y0 = Math.min(...partBoxes.map((b) => b.y)) - DEFAULT_BOX_MARGIN;
|
|
5624
|
+
const x1 = Math.max(...partBoxes.map((b) => b.x + b.w)) + DEFAULT_BOX_MARGIN;
|
|
5625
|
+
const y1 = Math.max(...partBoxes.map((b) => b.y + b.h)) + DEFAULT_BOX_MARGIN;
|
|
5626
|
+
return { x: x0, y: y0, w: x1 - x0, h: y1 - y0 };
|
|
5627
|
+
}
|
|
5628
|
+
if (!isObject(box)) {
|
|
5629
|
+
throw new BakeError(`${at}.box must be { x, y, w, h }, got ${show(box)}`);
|
|
5630
|
+
}
|
|
5631
|
+
checkedKeys(`${at}.box`, box, BOX_KEYS, BakeError);
|
|
5632
|
+
const [x, y] = ["x", "y"].map(
|
|
5633
|
+
(k) => checkedNumber(
|
|
5634
|
+
`${at}.box.${k}`,
|
|
5635
|
+
box[k],
|
|
5636
|
+
-Infinity,
|
|
5637
|
+
Infinity,
|
|
5638
|
+
false,
|
|
5639
|
+
BakeError
|
|
5640
|
+
)
|
|
5641
|
+
);
|
|
5642
|
+
const [w, h] = ["w", "h"].map(
|
|
5643
|
+
(k) => checkedNumber(`${at}.box.${k}`, box[k], 0, Infinity, true, BakeError)
|
|
5644
|
+
);
|
|
5645
|
+
return { x, y, w, h };
|
|
5646
|
+
}
|
|
5647
|
+
function checkStops(at, t, info, rest, cols, rows) {
|
|
5648
|
+
if (!isObject(t.stops)) {
|
|
5649
|
+
throw new BakeError(
|
|
5650
|
+
`${at}.stops must be an object keyed by a ${info.id} value, got ${show(t.stops)}`
|
|
5651
|
+
);
|
|
5652
|
+
}
|
|
5653
|
+
const n = (cols + 1) * (rows + 1);
|
|
5654
|
+
const stops = /* @__PURE__ */ new Map();
|
|
5655
|
+
for (const [key, list] of Object.entries(t.stops)) {
|
|
5656
|
+
const sat = `${at}.stops["${key}"]`;
|
|
5657
|
+
if (!PLAIN_DECIMAL.test(key)) {
|
|
5658
|
+
throw new BakeError(`${sat} must be keyed by a plain decimal number`);
|
|
5659
|
+
}
|
|
5660
|
+
const v = Number(key);
|
|
5661
|
+
if (!Number.isFinite(v) || v < info.min || v > info.max) {
|
|
5662
|
+
throw new BakeError(
|
|
5663
|
+
`${sat} is outside ${info.id}'s range ${info.min} to ${info.max}`
|
|
5664
|
+
);
|
|
5665
|
+
}
|
|
5666
|
+
if (Math.abs(v - rest) <= SAME_STOP) {
|
|
5667
|
+
throw new BakeError(
|
|
5668
|
+
`${sat} is ${info.id}'s rest value ${rest}: rest is implicit, leave it out`
|
|
5669
|
+
);
|
|
5670
|
+
}
|
|
5671
|
+
if (findStop(stops, v) !== void 0) {
|
|
5672
|
+
throw new BakeError(`${sat} repeats an earlier stop`);
|
|
5673
|
+
}
|
|
5674
|
+
if (!Array.isArray(list) || list.length !== n) {
|
|
5675
|
+
throw new BakeError(
|
|
5676
|
+
`${sat} must be ${n} points [dx, dy] (${cols + 1} x ${rows + 1}), got ${Array.isArray(list) ? `${list.length} points` : show(list)}`
|
|
5677
|
+
);
|
|
5678
|
+
}
|
|
5679
|
+
list.forEach((pt, k) => {
|
|
5680
|
+
if (!Array.isArray(pt) || pt.length !== 2) {
|
|
5681
|
+
throw new BakeError(`${sat}[${k}] must be [dx, dy], got ${show(pt)}`);
|
|
5682
|
+
}
|
|
5683
|
+
pt.forEach((c, j) => {
|
|
5684
|
+
if (!finite(c)) {
|
|
5685
|
+
throw new BakeError(
|
|
5686
|
+
`${sat}[${k}][${j}] must be a finite number, got ${String(c)}`
|
|
5687
|
+
);
|
|
5688
|
+
}
|
|
5689
|
+
});
|
|
5690
|
+
});
|
|
5691
|
+
stops.set(v, list);
|
|
5692
|
+
}
|
|
5693
|
+
if (stops.size === 0) {
|
|
5694
|
+
throw new BakeError(`${at}.stops must have at least one stop`);
|
|
5695
|
+
}
|
|
5696
|
+
if (t.mirror === true) {
|
|
5697
|
+
for (const [v, lattice2] of [...stops]) {
|
|
5698
|
+
const m = 2 * rest - v;
|
|
5699
|
+
if (m < info.min - SAME_STOP || m > info.max + SAME_STOP) continue;
|
|
5700
|
+
if (Math.abs(m - rest) <= SAME_STOP || findStop(stops, m) !== void 0) {
|
|
5701
|
+
continue;
|
|
5702
|
+
}
|
|
5703
|
+
stops.set(
|
|
5704
|
+
Math.min(info.max, Math.max(info.min, m)),
|
|
5705
|
+
mirrorLattice(lattice2, cols, rows)
|
|
5706
|
+
);
|
|
5707
|
+
}
|
|
5708
|
+
}
|
|
5709
|
+
return stops;
|
|
5710
|
+
}
|
|
5711
|
+
function checkTarget(t, i, customParts, paramById) {
|
|
5712
|
+
const at = `bakes.targets[${i}]`;
|
|
5713
|
+
if (!isObject(t)) {
|
|
5714
|
+
throw new BakeError(`${at} must be a plain object, got ${show(t)}`);
|
|
5715
|
+
}
|
|
5716
|
+
checkedKeys(at, t, TARGET_KEYS, BakeError);
|
|
5717
|
+
if (t.id !== void 0 && typeof t.id !== "string") {
|
|
5718
|
+
throw new BakeError(`${at}.id must be a string, got ${show(t.id)}`);
|
|
5719
|
+
}
|
|
5720
|
+
if (t.why !== void 0 && typeof t.why !== "string") {
|
|
5721
|
+
throw new BakeError(`${at}.why must be a string, got ${show(t.why)}`);
|
|
5722
|
+
}
|
|
5723
|
+
const info = typeof t.param === "string" ? paramById.get(t.param) : void 0;
|
|
5724
|
+
if (info === void 0) {
|
|
5725
|
+
throw new BakeError(
|
|
5726
|
+
`${at}.param must be a parameter the model declares, got ${show(t.param)}`
|
|
5727
|
+
);
|
|
5728
|
+
}
|
|
5729
|
+
const rest = Math.min(info.max, Math.max(info.min, info.default));
|
|
5730
|
+
const customIds = customParts.map((c) => c.id);
|
|
5731
|
+
if (!Array.isArray(t.parts) || t.parts.length === 0) {
|
|
5732
|
+
throw new BakeError(
|
|
5733
|
+
`${at}.parts must be a non-empty array of part ids, got ${show(t.parts)}`
|
|
5734
|
+
);
|
|
5735
|
+
}
|
|
5736
|
+
const parts = t.parts;
|
|
5737
|
+
parts.forEach((id, k) => {
|
|
5738
|
+
if (typeof id !== "string" || !customIds.includes(id)) {
|
|
5739
|
+
throw new BakeError(
|
|
5740
|
+
`${at}.parts[${k}] must be a custom part id (${customIds.join(", ")}): a bake moves custom parts only, got ${show(id)}`
|
|
5741
|
+
);
|
|
5742
|
+
}
|
|
5743
|
+
if (parts.indexOf(id) !== k) {
|
|
5744
|
+
throw new BakeError(`${at}.parts[${k}] repeats "${id}"`);
|
|
5745
|
+
}
|
|
5746
|
+
});
|
|
5747
|
+
const { cols, rows } = checkGrid(at, t.grid);
|
|
5748
|
+
const partBoxes = parts.map(
|
|
5749
|
+
(id) => customParts.find((c) => c.id === id).layer.bbox
|
|
5750
|
+
);
|
|
5751
|
+
const box = checkBox(at, t.box, partBoxes);
|
|
5752
|
+
if (t.mirror !== void 0 && typeof t.mirror !== "boolean") {
|
|
5753
|
+
throw new BakeError(
|
|
5754
|
+
`${at}.mirror must be true or false, got ${show(t.mirror)}`
|
|
5755
|
+
);
|
|
5756
|
+
}
|
|
5757
|
+
const stops = checkStops(at, t, info, rest, cols, rows);
|
|
5758
|
+
return {
|
|
5759
|
+
id: typeof t.id === "string" ? t.id : `target${i}`,
|
|
5760
|
+
param: info.id,
|
|
5761
|
+
rest,
|
|
5762
|
+
parts,
|
|
5763
|
+
box,
|
|
5764
|
+
cols,
|
|
5765
|
+
rows,
|
|
5766
|
+
stops
|
|
5767
|
+
};
|
|
5768
|
+
}
|
|
5769
|
+
function checkMesh(at, choice, box) {
|
|
5770
|
+
if (!isObject(choice)) {
|
|
5771
|
+
throw new BakeError(
|
|
5772
|
+
`${at} must be { rows, cols } or { cellPx }, got ${show(choice)}`
|
|
5773
|
+
);
|
|
5774
|
+
}
|
|
5775
|
+
checkedKeys(at, choice, ["rows", "cols", "cellPx"], BakeError);
|
|
5776
|
+
if (choice.cellPx !== void 0) {
|
|
5777
|
+
if (choice.rows !== void 0 || choice.cols !== void 0) {
|
|
5778
|
+
throw new BakeError(`${at} takes { cellPx } or { rows, cols }, not both`);
|
|
5779
|
+
}
|
|
5780
|
+
const cell = checkedNumber(
|
|
5781
|
+
`${at}.cellPx`,
|
|
5782
|
+
choice.cellPx,
|
|
5783
|
+
CELL_PX[0],
|
|
5784
|
+
CELL_PX[1],
|
|
5785
|
+
false,
|
|
5786
|
+
BakeError
|
|
5787
|
+
);
|
|
5788
|
+
const count = (Math.max(1, Math.round(box.w / cell)) + 1) * (Math.max(1, Math.round(box.h / cell)) + 1);
|
|
5789
|
+
if (count > MAX_VERTICES) {
|
|
5790
|
+
throw new BakeError(
|
|
5791
|
+
`${at}.cellPx ${cell} makes ${count} vertices, over the format's ${MAX_VERTICES} limit`
|
|
5792
|
+
);
|
|
5793
|
+
}
|
|
5794
|
+
return { cellPx: cell };
|
|
5795
|
+
}
|
|
5796
|
+
const cols = checkedNumber(
|
|
5797
|
+
`${at}.cols`,
|
|
5798
|
+
choice.cols,
|
|
5799
|
+
1,
|
|
5800
|
+
MAX_MESH_COLS,
|
|
5801
|
+
false,
|
|
5802
|
+
BakeError
|
|
5803
|
+
);
|
|
5804
|
+
if (!Number.isInteger(cols)) {
|
|
5805
|
+
throw new BakeError(`${at}.cols must be an integer, got ${cols}`);
|
|
5806
|
+
}
|
|
5807
|
+
const ys = choice.rows;
|
|
5808
|
+
if (!Array.isArray(ys) || ys.length < 2 || ys.length > MAX_MESH_ROWS) {
|
|
5809
|
+
throw new BakeError(
|
|
5810
|
+
`${at}.rows must be an array of 2 to ${MAX_MESH_ROWS} canvas-px y values, got ${Array.isArray(ys) ? `${ys.length} values` : show(ys)}`
|
|
5811
|
+
);
|
|
5812
|
+
}
|
|
5813
|
+
ys.forEach((y, k) => {
|
|
5814
|
+
if (!finite(y)) {
|
|
5815
|
+
throw new BakeError(
|
|
5816
|
+
`${at}.rows[${k}] must be a finite number, got ${String(y)}`
|
|
5817
|
+
);
|
|
5818
|
+
}
|
|
5819
|
+
if (k > 0 && !(y > ys[k - 1])) {
|
|
5820
|
+
throw new BakeError(
|
|
5821
|
+
`${at}.rows[${k}] must be below the row before it (rows are strictly increasing), got ${y} after ${String(ys[k - 1])}`
|
|
5822
|
+
);
|
|
5823
|
+
}
|
|
5824
|
+
});
|
|
5825
|
+
const last = ys.length - 1;
|
|
5826
|
+
if (Math.abs(ys[0] - box.y) > 0.5) {
|
|
5827
|
+
throw new BakeError(
|
|
5828
|
+
`${at}.rows[0] must be the part's box top ${r1(box.y)} (within 0.5 px), got ${String(ys[0])}`
|
|
5829
|
+
);
|
|
5830
|
+
}
|
|
5831
|
+
if (Math.abs(ys[last] - (box.y + box.h)) > 0.5) {
|
|
5832
|
+
throw new BakeError(
|
|
5833
|
+
`${at}.rows[${last}] must be the part's box bottom ${r1(box.y + box.h)} (within 0.5 px), got ${String(ys[last])}`
|
|
5834
|
+
);
|
|
5835
|
+
}
|
|
5836
|
+
return { rows: ys, cols };
|
|
5837
|
+
}
|
|
5838
|
+
function parseBakes(bakes, customParts, parameters) {
|
|
5839
|
+
if (customParts === void 0 || customParts.length === 0) {
|
|
5840
|
+
throw new BakeError(
|
|
5841
|
+
"bakes needs customParts: a bake moves custom parts only, and none were given"
|
|
5842
|
+
);
|
|
5843
|
+
}
|
|
5844
|
+
if (!isObject(bakes)) {
|
|
5845
|
+
throw new BakeError(`bakes must be a plain object, got ${show(bakes)}`);
|
|
5846
|
+
}
|
|
5847
|
+
checkedKeys("bakes", bakes, ["targets", "meshes"], BakeError);
|
|
5848
|
+
const { targets, meshes } = bakes;
|
|
5849
|
+
if (!Array.isArray(targets) || targets.length === 0) {
|
|
5850
|
+
throw new BakeError("bakes.targets must be a non-empty array");
|
|
5851
|
+
}
|
|
5852
|
+
const paramById = new Map(parameters.map((p) => [p.id, p]));
|
|
5853
|
+
const parsedTargets = targets.map(
|
|
5854
|
+
(t, i) => checkTarget(t, i, customParts, paramById)
|
|
5855
|
+
);
|
|
5856
|
+
const parsedMeshes = /* @__PURE__ */ new Map();
|
|
5857
|
+
if (meshes !== void 0) {
|
|
5858
|
+
if (!isObject(meshes)) {
|
|
5859
|
+
throw new BakeError(
|
|
5860
|
+
`bakes.meshes must be an object keyed by part id, got ${show(meshes)}`
|
|
5861
|
+
);
|
|
5862
|
+
}
|
|
5863
|
+
for (const [id, choice] of Object.entries(meshes)) {
|
|
5864
|
+
const at = `bakes.meshes.${id}`;
|
|
5865
|
+
const part = customParts.find((c) => c.id === id);
|
|
5866
|
+
if (part === void 0) {
|
|
5867
|
+
throw new BakeError(
|
|
5868
|
+
`${at}: "${id}" is not a custom part (${customParts.map((c) => c.id).join(", ")}): only a custom part is re-meshed`
|
|
5869
|
+
);
|
|
5870
|
+
}
|
|
5871
|
+
parsedMeshes.set(id, checkMesh(at, choice, part.layer.bbox));
|
|
5872
|
+
}
|
|
5873
|
+
}
|
|
5874
|
+
return { targets: parsedTargets, meshes: parsedMeshes };
|
|
5875
|
+
}
|
|
5876
|
+
var OFFSET_STEP = 1e-6;
|
|
5877
|
+
var RIGID_BEND = 25e-4;
|
|
5878
|
+
function rigidResidual(rest, moved) {
|
|
5879
|
+
const n = rest.length;
|
|
5880
|
+
if (moved.length !== n) {
|
|
5881
|
+
throw new Error(
|
|
5882
|
+
`rigidResidual: ${n} rest points but ${moved.length} moved points`
|
|
5883
|
+
);
|
|
5884
|
+
}
|
|
5885
|
+
if (n === 0) return 0;
|
|
5886
|
+
let pcx = 0;
|
|
5887
|
+
let pcy = 0;
|
|
5888
|
+
let qcx = 0;
|
|
5889
|
+
let qcy = 0;
|
|
5890
|
+
for (let i = 0; i < n; i++) {
|
|
5891
|
+
pcx += rest[i][0];
|
|
5892
|
+
pcy += rest[i][1];
|
|
5893
|
+
qcx += moved[i][0];
|
|
5894
|
+
qcy += moved[i][1];
|
|
5895
|
+
}
|
|
5896
|
+
pcx /= n;
|
|
5897
|
+
pcy /= n;
|
|
5898
|
+
qcx /= n;
|
|
5899
|
+
qcy /= n;
|
|
5900
|
+
let dot = 0;
|
|
5901
|
+
let cross = 0;
|
|
5902
|
+
for (let i = 0; i < n; i++) {
|
|
5903
|
+
const px = rest[i][0] - pcx;
|
|
5904
|
+
const py = rest[i][1] - pcy;
|
|
5905
|
+
const qx = moved[i][0] - qcx;
|
|
5906
|
+
const qy = moved[i][1] - qcy;
|
|
5907
|
+
dot += px * qx + py * qy;
|
|
5908
|
+
cross += px * qy - py * qx;
|
|
5909
|
+
}
|
|
5910
|
+
const theta = Math.atan2(cross, dot);
|
|
5911
|
+
const c = Math.cos(theta);
|
|
5912
|
+
const s = Math.sin(theta);
|
|
5913
|
+
let worst = 0;
|
|
5914
|
+
for (let i = 0; i < n; i++) {
|
|
5915
|
+
const px = rest[i][0] - pcx;
|
|
5916
|
+
const py = rest[i][1] - pcy;
|
|
5917
|
+
const ex = qcx + c * px - s * py - moved[i][0];
|
|
5918
|
+
const ey = qcy + s * px + c * py - moved[i][1];
|
|
5919
|
+
worst = Math.max(worst, Math.hypot(ex, ey));
|
|
5920
|
+
}
|
|
5921
|
+
return worst;
|
|
5922
|
+
}
|
|
5923
|
+
function latticeAt(t, lattice2, x, y) {
|
|
5924
|
+
const { cols, rows, box } = t;
|
|
5925
|
+
const u = clamp((x - box.x) / box.w, 0, 1) * cols;
|
|
5926
|
+
const v = clamp((y - box.y) / box.h, 0, 1) * rows;
|
|
5927
|
+
const c = Math.min(Math.floor(u), cols - 1);
|
|
5928
|
+
const r = Math.min(Math.floor(v), rows - 1);
|
|
5929
|
+
const fx = u - c;
|
|
5930
|
+
const fy = v - r;
|
|
5931
|
+
const at = (rr, cc) => lattice2[rr * (cols + 1) + cc];
|
|
5932
|
+
const mix = (k) => {
|
|
5933
|
+
const top = at(r, c)[k] + (at(r, c + 1)[k] - at(r, c)[k]) * fx;
|
|
5934
|
+
const bot = at(r + 1, c)[k] + (at(r + 1, c + 1)[k] - at(r + 1, c)[k]) * fx;
|
|
5935
|
+
return top + (bot - top) * fy;
|
|
5936
|
+
};
|
|
5937
|
+
return [mix(0), mix(1)];
|
|
5938
|
+
}
|
|
5939
|
+
function targetField(t, v) {
|
|
5940
|
+
const side = Math.sign(v - t.rest);
|
|
5941
|
+
const knots = [...t.stops.keys()].filter((k) => Math.sign(k - t.rest) === side).sort((a2, b) => Math.abs(a2 - t.rest) - Math.abs(b - t.rest));
|
|
5942
|
+
const a = Math.abs(v - t.rest);
|
|
5943
|
+
return (x, y) => {
|
|
5944
|
+
if (knots.length === 0) return [0, 0];
|
|
5945
|
+
const at = (k) => latticeAt(t, t.stops.get(k), x, y);
|
|
5946
|
+
let lo = 0;
|
|
5947
|
+
let loVal = [0, 0];
|
|
5948
|
+
for (const k of knots) {
|
|
5949
|
+
const hi = Math.abs(k - t.rest);
|
|
5950
|
+
const hiVal = at(k);
|
|
5951
|
+
if (a <= hi + SAME_STOP) {
|
|
5952
|
+
const f = hi - lo > 0 ? Math.min(1, (a - lo) / (hi - lo)) : 1;
|
|
5953
|
+
return [
|
|
5954
|
+
loVal[0] + (hiVal[0] - loVal[0]) * f,
|
|
5955
|
+
loVal[1] + (hiVal[1] - loVal[1]) * f
|
|
5956
|
+
];
|
|
5957
|
+
}
|
|
5958
|
+
lo = hi;
|
|
5959
|
+
loVal = hiVal;
|
|
5960
|
+
}
|
|
5961
|
+
return loVal;
|
|
5962
|
+
};
|
|
5963
|
+
}
|
|
5964
|
+
var apply = (m, x, y) => [
|
|
5965
|
+
m[0] * x + m[2] * y + m[4],
|
|
5966
|
+
m[1] * x + m[3] * y + m[5]
|
|
5967
|
+
];
|
|
5968
|
+
function invert(m) {
|
|
5969
|
+
const det = m[0] * m[3] - m[1] * m[2];
|
|
5970
|
+
const a = m[3] / det;
|
|
5971
|
+
const b = -m[1] / det;
|
|
5972
|
+
const c = -m[2] / det;
|
|
5973
|
+
const d = m[0] / det;
|
|
5974
|
+
return [a, b, c, d, -(a * m[4] + c * m[5]), -(b * m[4] + d * m[5])];
|
|
5975
|
+
}
|
|
5976
|
+
function remesh(part, entry, choice, canvas) {
|
|
5977
|
+
const b = boxOfPixels(entry.layer.bbox, canvas.width, canvas.height);
|
|
5978
|
+
if ("cellPx" in choice) {
|
|
5979
|
+
part.mesh = gridMesh(
|
|
5980
|
+
Math.max(1, Math.round(bw(b) / choice.cellPx)),
|
|
5981
|
+
Math.max(1, Math.round(bh(b) / choice.cellPx))
|
|
5982
|
+
);
|
|
5983
|
+
return;
|
|
5984
|
+
}
|
|
5985
|
+
const xs = Array.from(
|
|
5986
|
+
{ length: choice.cols + 1 },
|
|
5987
|
+
(_, c) => b.x0 + bw(b) * c / choice.cols
|
|
5988
|
+
);
|
|
5989
|
+
part.mesh = columnMesh(b, {
|
|
5990
|
+
xs,
|
|
5991
|
+
ys: choice.rows.map((y) => canvas.height / 2 - y)
|
|
5992
|
+
});
|
|
5993
|
+
}
|
|
5994
|
+
function applyBakes(parsed, parts, entries, ctx) {
|
|
5995
|
+
const { canvas, deformers: deformers2, parameters, faceWidth } = ctx;
|
|
5996
|
+
const partOf = (id) => parts.find((p) => p.id === id);
|
|
5997
|
+
const entryOf = (id) => entries.find((e) => e.id === id);
|
|
5998
|
+
for (const [id, choice] of parsed.meshes) {
|
|
5999
|
+
remesh(partOf(id), entryOf(id), choice, canvas);
|
|
6000
|
+
}
|
|
6001
|
+
const ids = [...new Set(parsed.targets.flatMap((t) => t.parts))];
|
|
6002
|
+
const reports = [];
|
|
6003
|
+
for (const id of ids) {
|
|
6004
|
+
const part = partOf(id);
|
|
6005
|
+
const n = part.mesh.vertices.length / 2;
|
|
6006
|
+
const own = parsed.targets.filter((t) => t.parts.includes(id));
|
|
6007
|
+
const w0 = matrixWorldAt(deformers2, parameters, part.deformer);
|
|
6008
|
+
const local = [];
|
|
6009
|
+
const restModel = [];
|
|
6010
|
+
const restCanvas = [];
|
|
6011
|
+
for (let i = 0; i < n; i++) {
|
|
6012
|
+
const p = [
|
|
6013
|
+
part.transform.x + part.mesh.vertices[i * 2] * part.width,
|
|
6014
|
+
part.transform.y + part.mesh.vertices[i * 2 + 1] * part.height
|
|
6015
|
+
];
|
|
6016
|
+
const r = apply(w0, p[0], p[1]);
|
|
6017
|
+
local.push(p);
|
|
6018
|
+
restModel.push(r);
|
|
6019
|
+
restCanvas.push([r[0] + canvas.width / 2, canvas.height / 2 - r[1]]);
|
|
6020
|
+
}
|
|
6021
|
+
for (const param of [...new Set(own.map((t) => t.param))]) {
|
|
6022
|
+
const targets = own.filter((t) => t.param === param);
|
|
6023
|
+
const rest = targets[0].rest;
|
|
6024
|
+
const stops = [];
|
|
6025
|
+
for (const v of targets.flatMap((t) => [...t.stops.keys()]).sort((a, b) => a - b)) {
|
|
6026
|
+
if (stops.length === 0 || v - stops[stops.length - 1] > SAME_STOP) {
|
|
6027
|
+
stops.push(v);
|
|
6028
|
+
}
|
|
6029
|
+
}
|
|
6030
|
+
const range = parameters.find((p) => p.id === param);
|
|
6031
|
+
const ends = [];
|
|
6032
|
+
for (const [outer, end] of [
|
|
6033
|
+
[stops.filter((v) => v < rest).shift(), range.min],
|
|
6034
|
+
[stops.filter((v) => v > rest).pop(), range.max]
|
|
6035
|
+
]) {
|
|
6036
|
+
if (outer !== void 0 && Math.abs(end - outer) > SAME_STOP) {
|
|
6037
|
+
ends.push(end);
|
|
6038
|
+
}
|
|
6039
|
+
}
|
|
6040
|
+
const keyforms = [{ value: rest, offsets: new Array(2 * n).fill(0) }];
|
|
6041
|
+
const wanted = [];
|
|
6042
|
+
const endWanted = [];
|
|
6043
|
+
for (const v of [...stops, ...ends]) {
|
|
6044
|
+
const disp = new Array(2 * n).fill(0);
|
|
6045
|
+
const fields2 = targets.map((t) => targetField(t, v));
|
|
6046
|
+
for (let i = 0; i < n; i++) {
|
|
6047
|
+
for (const field of fields2) {
|
|
6048
|
+
const [dx, dy] = field(restCanvas[i][0], restCanvas[i][1]);
|
|
6049
|
+
disp[i * 2] += dx;
|
|
6050
|
+
disp[i * 2 + 1] += dy;
|
|
6051
|
+
}
|
|
6052
|
+
}
|
|
6053
|
+
(ends.includes(v) ? endWanted : wanted).push(disp);
|
|
6054
|
+
if (entryOf(id).rigid === true) {
|
|
6055
|
+
const moved = restCanvas.map(
|
|
6056
|
+
(p, i) => [p[0] + disp[i * 2], p[1] + disp[i * 2 + 1]]
|
|
6057
|
+
);
|
|
6058
|
+
const bend = rigidResidual(restCanvas, moved);
|
|
6059
|
+
if (bend > RIGID_BEND * faceWidth) {
|
|
6060
|
+
const names = targets.map(
|
|
6061
|
+
(t) => `bakes.targets[${parsed.targets.indexOf(t)}] ("${t.id}")`
|
|
6062
|
+
).join(", ");
|
|
6063
|
+
throw new BakeError(
|
|
6064
|
+
`${names}: "${id}" is rigid, but at ${param} ${v} these targets together bend it by ${bend.toFixed(2)} px, over the ${(RIGID_BEND * faceWidth).toFixed(2)} px a rigid part may bend (${RIGID_BEND} face widths): give every control point one displacement, or a rotation`
|
|
6065
|
+
);
|
|
6066
|
+
}
|
|
6067
|
+
}
|
|
6068
|
+
const inv = invert(
|
|
6069
|
+
matrixWorldAt(deformers2, parameters, part.deformer, { [param]: v })
|
|
6070
|
+
);
|
|
6071
|
+
const offsets = [];
|
|
6072
|
+
for (let i = 0; i < n; i++) {
|
|
6073
|
+
const [px, py] = apply(
|
|
6074
|
+
inv,
|
|
6075
|
+
restModel[i][0] + disp[i * 2],
|
|
6076
|
+
restModel[i][1] - disp[i * 2 + 1]
|
|
6077
|
+
);
|
|
6078
|
+
offsets.push(
|
|
6079
|
+
roundTo((px - local[i][0]) / part.width, OFFSET_STEP),
|
|
6080
|
+
roundTo((py - local[i][1]) / part.height, OFFSET_STEP)
|
|
6081
|
+
);
|
|
6082
|
+
}
|
|
6083
|
+
keyforms.push({ value: v, offsets });
|
|
6084
|
+
}
|
|
6085
|
+
keyforms.sort((a, b) => a.value - b.value);
|
|
6086
|
+
part.warps = [...part.warps ?? [], { parameter: param, keyforms }];
|
|
6087
|
+
reports.push({
|
|
6088
|
+
part: id,
|
|
6089
|
+
param,
|
|
6090
|
+
stops,
|
|
6091
|
+
vertexCount: n,
|
|
6092
|
+
wanted,
|
|
6093
|
+
ends: ends.map((value, i) => ({ value, wanted: endWanted[i] }))
|
|
6094
|
+
});
|
|
6095
|
+
}
|
|
6096
|
+
}
|
|
6097
|
+
return reports;
|
|
6098
|
+
}
|
|
6099
|
+
|
|
5407
6100
|
// src/auto-rig/generate.ts
|
|
5408
6101
|
var import_format12 = require("@ikijs/format");
|
|
5409
6102
|
|
|
@@ -5606,8 +6299,8 @@ function defaultMotions(declared) {
|
|
|
5606
6299
|
}
|
|
5607
6300
|
|
|
5608
6301
|
// src/auto-rig/checks.ts
|
|
5609
|
-
var
|
|
5610
|
-
var
|
|
6302
|
+
var finite2 = (v) => typeof v === "number" && Number.isFinite(v);
|
|
6303
|
+
var isObject2 = (v) => typeof v === "object" && v !== null;
|
|
5611
6304
|
var MIN_FACE_PX = 24;
|
|
5612
6305
|
function checkLayers(layers, canvas) {
|
|
5613
6306
|
if (!Array.isArray(layers) || layers.length === 0) {
|
|
@@ -5615,10 +6308,10 @@ function checkLayers(layers, canvas) {
|
|
|
5615
6308
|
"auto-rig: generateIkiFromLayerSet: layers must be a non-empty array"
|
|
5616
6309
|
);
|
|
5617
6310
|
}
|
|
5618
|
-
if (!(
|
|
6311
|
+
if (!(finite2(canvas?.width) && canvas.width > 0)) {
|
|
5619
6312
|
throw new Error("auto-rig: canvas.width must be a positive number");
|
|
5620
6313
|
}
|
|
5621
|
-
if (!(
|
|
6314
|
+
if (!(finite2(canvas?.height) && canvas.height > 0)) {
|
|
5622
6315
|
throw new Error("auto-rig: canvas.height must be a positive number");
|
|
5623
6316
|
}
|
|
5624
6317
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -5630,7 +6323,7 @@ function checkLayers(layers, canvas) {
|
|
|
5630
6323
|
seen.add(l.role);
|
|
5631
6324
|
const at = `auto-rig: layer "${l.fileName}"`;
|
|
5632
6325
|
const { x, y, w, h } = l.bbox ?? {};
|
|
5633
|
-
if (![x, y, w, h].every(
|
|
6326
|
+
if (![x, y, w, h].every(finite2) || w <= 0 || h <= 0 || x < 0 || y < 0 || x + w > canvas.width || y + h > canvas.height) {
|
|
5634
6327
|
throw new Error(
|
|
5635
6328
|
`${at} has a bbox outside its ${canvas.width}x${canvas.height} canvas`
|
|
5636
6329
|
);
|
|
@@ -5658,7 +6351,7 @@ function checkLayers(layers, canvas) {
|
|
|
5658
6351
|
);
|
|
5659
6352
|
}
|
|
5660
6353
|
rows.forEach((v, i) => {
|
|
5661
|
-
if (!
|
|
6354
|
+
if (!finite2(v) || v < 0 || v > w / 2 + 0.5) {
|
|
5662
6355
|
throw new Error(
|
|
5663
6356
|
`${at}: rowHalfWidths[${i}] ${v} is not a half-width inside its crop`
|
|
5664
6357
|
);
|
|
@@ -5699,7 +6392,7 @@ function checkLayers(layers, canvas) {
|
|
|
5699
6392
|
}
|
|
5700
6393
|
if (l.denseCore !== void 0) {
|
|
5701
6394
|
const c = l.denseCore;
|
|
5702
|
-
if (![c.x, c.y, c.w, c.h].every(
|
|
6395
|
+
if (![c.x, c.y, c.w, c.h].every(finite2) || c.w <= 0 || c.h <= 0 || c.x < x - 1 || c.y < y - 1 || c.x + c.w > x + w + 1 || c.y + c.h > y + h + 1) {
|
|
5703
6396
|
throw new Error(`${at}: denseCore is not a box inside its crop`);
|
|
5704
6397
|
}
|
|
5705
6398
|
}
|
|
@@ -5722,7 +6415,7 @@ function checkLayers(layers, canvas) {
|
|
|
5722
6415
|
}
|
|
5723
6416
|
function checkHeadEdges(edges, byRole) {
|
|
5724
6417
|
if (edges === void 0) return;
|
|
5725
|
-
if (!
|
|
6418
|
+
if (!isObject2(edges))
|
|
5726
6419
|
throw new TurnTargetError("headEdges must be an object");
|
|
5727
6420
|
for (const side of ["left", "right"]) {
|
|
5728
6421
|
const list = edges[side];
|
|
@@ -5730,12 +6423,12 @@ function checkHeadEdges(edges, byRole) {
|
|
|
5730
6423
|
throw new TurnTargetError(`headEdges.${side} must be an array`);
|
|
5731
6424
|
}
|
|
5732
6425
|
list.forEach((e, i) => {
|
|
5733
|
-
if (!
|
|
6426
|
+
if (!isObject2(e) || typeof e.role !== "string" || !byRole.has(e.role)) {
|
|
5734
6427
|
throw new TurnTargetError(
|
|
5735
6428
|
`headEdges.${side}[${i}].role is not a layer in this set`
|
|
5736
6429
|
);
|
|
5737
6430
|
}
|
|
5738
|
-
if (!
|
|
6431
|
+
if (!finite2(e.x)) {
|
|
5739
6432
|
throw new TurnTargetError(
|
|
5740
6433
|
`headEdges.${side}[${i}].x must be a finite number`
|
|
5741
6434
|
);
|
|
@@ -5755,25 +6448,25 @@ function checkHeadEdges(edges, byRole) {
|
|
|
5755
6448
|
}
|
|
5756
6449
|
function checkStrandEdges(strand, irises, hair) {
|
|
5757
6450
|
if (strand === void 0) return;
|
|
5758
|
-
if (!
|
|
6451
|
+
if (!isObject2(strand)) {
|
|
5759
6452
|
throw new TurnTargetError("strandEdges must be an object");
|
|
5760
6453
|
}
|
|
5761
6454
|
for (const side of ["left", "right"]) {
|
|
5762
6455
|
const s = strand[side];
|
|
5763
6456
|
if (s === void 0) continue;
|
|
5764
6457
|
const at = `strandEdges.${side}`;
|
|
5765
|
-
if (!
|
|
6458
|
+
if (!isObject2(s)) throw new TurnTargetError(`${at} must be an object`);
|
|
5766
6459
|
if (hair === void 0) {
|
|
5767
6460
|
throw new TurnTargetError(
|
|
5768
6461
|
`${at} names a bangs' run, but this layer set has no hair_front`
|
|
5769
6462
|
);
|
|
5770
6463
|
}
|
|
5771
6464
|
for (const k of ["y", "irisOuter", "irisInner", "runOuter"]) {
|
|
5772
|
-
if (!
|
|
6465
|
+
if (!finite2(s[k])) {
|
|
5773
6466
|
throw new TurnTargetError(`${at}.${k} must be a finite number`);
|
|
5774
6467
|
}
|
|
5775
6468
|
}
|
|
5776
|
-
if (s.runFace !== null && !
|
|
6469
|
+
if (s.runFace !== null && !finite2(s.runFace)) {
|
|
5777
6470
|
throw new TurnTargetError(
|
|
5778
6471
|
`${at}.runFace must be a finite number or null`
|
|
5779
6472
|
);
|
|
@@ -6242,9 +6935,9 @@ function replay(op, i, ctx, sums) {
|
|
|
6242
6935
|
const x = span2(`${name}.region.x`, region.x);
|
|
6243
6936
|
const y = span2(`${name}.region.y`, region.y);
|
|
6244
6937
|
const by = fields(`${name}.by`, o.by, BY_KEYS);
|
|
6245
|
-
const dx =
|
|
6246
|
-
const dy =
|
|
6247
|
-
const falloff = o.falloff === void 0 ? 0 :
|
|
6938
|
+
const dx = finite3(`${name}.by.dx`, by.dx);
|
|
6939
|
+
const dy = finite3(`${name}.by.dy`, by.dy);
|
|
6940
|
+
const falloff = o.falloff === void 0 ? 0 : finite3(`${name}.falloff`, o.falloff, 0);
|
|
6248
6941
|
if (o.why !== void 0) text(`${name}.why`, o.why);
|
|
6249
6942
|
const box = ctx.boxOf(role);
|
|
6250
6943
|
if (box === void 0) {
|
|
@@ -6412,10 +7105,10 @@ function fields(name, v, known) {
|
|
|
6412
7105
|
function numbers(name, v) {
|
|
6413
7106
|
const o = fields(name, v, Object.keys(v ?? {}));
|
|
6414
7107
|
return Object.fromEntries(
|
|
6415
|
-
Object.entries(o).map(([k, n]) => [k,
|
|
7108
|
+
Object.entries(o).map(([k, n]) => [k, finite3(`${name}.${k}`, n)])
|
|
6416
7109
|
);
|
|
6417
7110
|
}
|
|
6418
|
-
var
|
|
7111
|
+
var finite3 = (name, v, lo = -Infinity) => asPatch(() => checkedNumber(name, v, lo, Infinity));
|
|
6419
7112
|
function text(name, v) {
|
|
6420
7113
|
if (typeof v !== "string") {
|
|
6421
7114
|
throw new PatchError(`${name} must be a string, got ${String(v)}`);
|
|
@@ -6428,8 +7121,8 @@ function span2(name, v) {
|
|
|
6428
7121
|
`${name} must be [from, to], got ${JSON.stringify(v)}`
|
|
6429
7122
|
);
|
|
6430
7123
|
}
|
|
6431
|
-
const lo =
|
|
6432
|
-
const hi =
|
|
7124
|
+
const lo = finite3(`${name}[0]`, v[0]);
|
|
7125
|
+
const hi = finite3(`${name}[1]`, v[1]);
|
|
6433
7126
|
if (lo > hi) {
|
|
6434
7127
|
throw new PatchError(
|
|
6435
7128
|
`${name} [${lo}, ${hi}] runs backwards: ${name}[0] must not exceed ${name}[1]`
|
|
@@ -6438,6 +7131,112 @@ function span2(name, v) {
|
|
|
6438
7131
|
return [lo, hi];
|
|
6439
7132
|
}
|
|
6440
7133
|
|
|
7134
|
+
// src/auto-rig/custom-parts.ts
|
|
7135
|
+
var KEYS = ["id", "layer", "anchor", "tie", "after", "deformer", "rigid"];
|
|
7136
|
+
var LAYER_KEYS = ["fileName", "bbox"];
|
|
7137
|
+
var finite4 = (v) => typeof v === "number" && Number.isFinite(v);
|
|
7138
|
+
var isObject3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
7139
|
+
var inside = (b, [x, y]) => x >= b.x && x < b.x + b.w && y >= b.y && y < b.y + b.h;
|
|
7140
|
+
var RESERVED = new Set(
|
|
7141
|
+
ROLE_TABLE.flatMap((r) => [r.role, ...partIdsOfRole(r.role)])
|
|
7142
|
+
);
|
|
7143
|
+
function checkCustomParts(customParts, byRole, canvas) {
|
|
7144
|
+
if (!Array.isArray(customParts)) {
|
|
7145
|
+
throw new CustomPartError(
|
|
7146
|
+
`customParts must be an array, got ${customParts === null ? "null" : typeof customParts}`
|
|
7147
|
+
);
|
|
7148
|
+
}
|
|
7149
|
+
const present = [...byRole.keys()];
|
|
7150
|
+
const roleField = (at, v) => {
|
|
7151
|
+
const layer = typeof v === "string" ? byRole.get(v) : void 0;
|
|
7152
|
+
if (layer === void 0) {
|
|
7153
|
+
throw new CustomPartError(
|
|
7154
|
+
`${at} must be a role of the set, one of ${present.join(", ")}; got ${JSON.stringify(v)}`
|
|
7155
|
+
);
|
|
7156
|
+
}
|
|
7157
|
+
return layer;
|
|
7158
|
+
};
|
|
7159
|
+
const ids = /* @__PURE__ */ new Set();
|
|
7160
|
+
customParts.forEach((entry, i) => {
|
|
7161
|
+
const at = `customParts[${i}]`;
|
|
7162
|
+
if (!isObject3(entry)) {
|
|
7163
|
+
throw new CustomPartError(`${at} must be a plain object`);
|
|
7164
|
+
}
|
|
7165
|
+
checkedKeys(at, entry, KEYS, CustomPartError);
|
|
7166
|
+
const { id, layer, anchor, tie, after, deformer, rigid } = entry;
|
|
7167
|
+
if (typeof id !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(id)) {
|
|
7168
|
+
throw new CustomPartError(
|
|
7169
|
+
`${at}.id must be a plain identifier (letters, digits, underscore; not starting with a digit), got ${JSON.stringify(id)}`
|
|
7170
|
+
);
|
|
7171
|
+
}
|
|
7172
|
+
if (RESERVED.has(id)) {
|
|
7173
|
+
throw new CustomPartError(
|
|
7174
|
+
`${at}.id "${id}" is a role's name or a part id a role yields: those are reserved, also for roles added later, so pick a name no role would take`
|
|
7175
|
+
);
|
|
7176
|
+
}
|
|
7177
|
+
if (ids.has(id)) {
|
|
7178
|
+
throw new CustomPartError(
|
|
7179
|
+
`${at}.id "${id}" repeats an earlier entry's id`
|
|
7180
|
+
);
|
|
7181
|
+
}
|
|
7182
|
+
ids.add(id);
|
|
7183
|
+
if (!isObject3(layer)) {
|
|
7184
|
+
throw new CustomPartError(`${at}.layer must be a plain object`);
|
|
7185
|
+
}
|
|
7186
|
+
checkedKeys(`${at}.layer`, layer, LAYER_KEYS, CustomPartError);
|
|
7187
|
+
if (typeof layer.fileName !== "string" || layer.fileName === "") {
|
|
7188
|
+
throw new CustomPartError(
|
|
7189
|
+
`${at}.layer.fileName must be a non-empty string`
|
|
7190
|
+
);
|
|
7191
|
+
}
|
|
7192
|
+
const b = isObject3(layer.bbox) ? layer.bbox : {};
|
|
7193
|
+
const { x, y, w, h } = b;
|
|
7194
|
+
if (!(finite4(x) && finite4(y) && finite4(w) && finite4(h)) || w <= 0 || h <= 0 || x < 0 || y < 0 || x + w > canvas.width || y + h > canvas.height) {
|
|
7195
|
+
throw new CustomPartError(
|
|
7196
|
+
`${at}.layer.bbox must be a finite, non-empty box inside the ${canvas.width}x${canvas.height} canvas, got ${JSON.stringify(layer.bbox)}`
|
|
7197
|
+
);
|
|
7198
|
+
}
|
|
7199
|
+
const box = { x, y, w, h };
|
|
7200
|
+
const anchorLayer = roleField(`${at}.anchor`, anchor);
|
|
7201
|
+
roleField(`${at}.after`, after);
|
|
7202
|
+
if (!Array.isArray(tie) || tie.length !== 2 || !tie.every(finite4) || !inside(box, tie) || !inside(anchorLayer.bbox, tie)) {
|
|
7203
|
+
throw new CustomPartError(
|
|
7204
|
+
`${at}.tie must be [x, y], two finite canvas px inside both the part's layer box and the anchor "${String(anchor)}" layer box, got ${JSON.stringify(tie)}`
|
|
7205
|
+
);
|
|
7206
|
+
}
|
|
7207
|
+
if (deformer !== void 0 && deformer !== "headDeformer") {
|
|
7208
|
+
throw new CustomPartError(
|
|
7209
|
+
`${at}.deformer "${String(deformer)}" is not accepted: headDeformer is the only deformer a custom part hangs on`
|
|
7210
|
+
);
|
|
7211
|
+
}
|
|
7212
|
+
if (rigid !== void 0 && typeof rigid !== "boolean") {
|
|
7213
|
+
throw new CustomPartError(
|
|
7214
|
+
`${at}.rigid must be true or false, got ${JSON.stringify(rigid)}`
|
|
7215
|
+
);
|
|
7216
|
+
}
|
|
7217
|
+
});
|
|
7218
|
+
return customParts;
|
|
7219
|
+
}
|
|
7220
|
+
var CELL_PX2 = 20;
|
|
7221
|
+
function buildCustomParts(customParts, canvas) {
|
|
7222
|
+
return customParts.map(({ id, layer }) => {
|
|
7223
|
+
const b = boxOfPixels(layer.bbox, canvas.width, canvas.height);
|
|
7224
|
+
return {
|
|
7225
|
+
id,
|
|
7226
|
+
color: [1, 1, 1, 1],
|
|
7227
|
+
width: bw(b),
|
|
7228
|
+
height: bh(b),
|
|
7229
|
+
transform: { x: cx(b), y: cy(b) },
|
|
7230
|
+
order: 0,
|
|
7231
|
+
deformer: "headDeformer",
|
|
7232
|
+
mesh: gridMesh(
|
|
7233
|
+
Math.max(1, Math.round(bw(b) / CELL_PX2)),
|
|
7234
|
+
Math.max(1, Math.round(bh(b) / CELL_PX2))
|
|
7235
|
+
)
|
|
7236
|
+
};
|
|
7237
|
+
});
|
|
7238
|
+
}
|
|
7239
|
+
|
|
6441
7240
|
// src/auto-rig/generate.ts
|
|
6442
7241
|
var MESH_CELLS = {
|
|
6443
7242
|
face: { px: 28, min: 4, max: 24 },
|
|
@@ -6479,6 +7278,9 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
6479
7278
|
const hasNose = has("nose");
|
|
6480
7279
|
const plateHalf = byRole.get("face").cropW / 2;
|
|
6481
7280
|
checkHeadEdges(options.headEdges, byRole);
|
|
7281
|
+
const entries = options.customParts !== void 0 && !(Array.isArray(options.customParts) && options.customParts.length === 0) ? checkCustomParts(options.customParts, byRole, canvas) : [];
|
|
7282
|
+
const parameters = declareParameters(new Set(byRole.keys()));
|
|
7283
|
+
const bakes = options.bakes === void 0 ? void 0 : parseBakes(options.bakes, entries, parameters);
|
|
6482
7284
|
checkStrandEdges(
|
|
6483
7285
|
options.strandEdges,
|
|
6484
7286
|
sideIrises(byRole),
|
|
@@ -6628,7 +7430,6 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
6628
7430
|
...part.warps ?? []
|
|
6629
7431
|
];
|
|
6630
7432
|
}
|
|
6631
|
-
const parameters = declareParameters(new Set(byRole.keys()));
|
|
6632
7433
|
const reports = options.patches === void 0 ? void 0 : applyPatches(options.patches, {
|
|
6633
7434
|
grids: new Map(
|
|
6634
7435
|
[...grids].map(([family, g]) => [WARP_ID[family], g])
|
|
@@ -6638,19 +7439,50 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
6638
7439
|
faceWidth: byRole.get("face").cropW,
|
|
6639
7440
|
parameters
|
|
6640
7441
|
});
|
|
7442
|
+
const deformerList = deformers(
|
|
7443
|
+
frame,
|
|
7444
|
+
grids,
|
|
7445
|
+
bodyWarp?.deformer,
|
|
7446
|
+
arms,
|
|
7447
|
+
byRole
|
|
7448
|
+
);
|
|
7449
|
+
let bakeReports;
|
|
7450
|
+
if (entries.length > 0) {
|
|
7451
|
+
const built = buildCustomParts(entries, canvas);
|
|
7452
|
+
entries.forEach((entry, i) => {
|
|
7453
|
+
const group = new Set(partIdsOfRole(entry.after));
|
|
7454
|
+
for (let j = 0; j < i; j++) {
|
|
7455
|
+
if (entries[j].after === entry.after) group.add(entries[j].id);
|
|
7456
|
+
}
|
|
7457
|
+
const last = parts.map((p) => group.has(p.id)).lastIndexOf(true);
|
|
7458
|
+
parts.splice(last + 1, 0, built[i]);
|
|
7459
|
+
});
|
|
7460
|
+
parts.forEach((p, i) => {
|
|
7461
|
+
p.order = i;
|
|
7462
|
+
});
|
|
7463
|
+
if (bakes !== void 0) {
|
|
7464
|
+
bakeReports = applyBakes(bakes, built, entries, {
|
|
7465
|
+
canvas,
|
|
7466
|
+
deformers: deformerList,
|
|
7467
|
+
parameters,
|
|
7468
|
+
faceWidth: byRole.get("face").cropW
|
|
7469
|
+
});
|
|
7470
|
+
}
|
|
7471
|
+
}
|
|
6641
7472
|
const model = {
|
|
6642
7473
|
version: import_format12.IKI_FORMAT_VERSION,
|
|
6643
7474
|
name: "auto-rigged",
|
|
6644
7475
|
canvas: { width: canvas.width, height: canvas.height },
|
|
6645
7476
|
parameters,
|
|
6646
7477
|
parts,
|
|
6647
|
-
deformers:
|
|
7478
|
+
deformers: deformerList,
|
|
6648
7479
|
...has("hair_front") ? { physics: hairPhysics(has("hair_back")) } : {},
|
|
6649
7480
|
expressions: defaultExpressions(new Set(parameters.map((p) => p.id))),
|
|
6650
7481
|
motions: defaultMotions(new Set(parameters.map((p) => p.id)))
|
|
6651
7482
|
};
|
|
6652
7483
|
const valid = (0, import_format12.parseIkiModel)(model);
|
|
6653
7484
|
if (reports !== void 0) options.onPatchesApplied?.(reports);
|
|
7485
|
+
if (bakeReports !== void 0) options.onBakesApplied?.(bakeReports);
|
|
6654
7486
|
options.onRigPlanResolved?.(
|
|
6655
7487
|
rigPlanReport(options.rigPlan ?? {}, plan, targets, fit)
|
|
6656
7488
|
);
|
|
@@ -6880,14 +7712,18 @@ function buildPart(spec, b, order, frame, byRole, plan, frontGrid, lips) {
|
|
|
6880
7712
|
break;
|
|
6881
7713
|
case "hair_side_L":
|
|
6882
7714
|
case "hair_side_R": {
|
|
6883
|
-
const
|
|
6884
|
-
|
|
6885
|
-
weight: sideRootBlend(frame, spec.role, b).weight
|
|
6886
|
-
};
|
|
7715
|
+
const { rootEnd, weight } = sideRootBlend(frame, spec.role, b);
|
|
7716
|
+
const attach = { box: box("hair_front"), weight };
|
|
6887
7717
|
warps.push(
|
|
6888
7718
|
lockSway(import_format12.StandardParameter.HairSwayX, mesh, b, swayAmp, attach),
|
|
6889
7719
|
lockSway(import_format12.StandardParameter.HairSwayZ, mesh, b, swayAmp, attach),
|
|
6890
|
-
|
|
7720
|
+
// Under a roll it swings about its root, where it leaves the bangs.
|
|
7721
|
+
tiltHang(
|
|
7722
|
+
mesh,
|
|
7723
|
+
b,
|
|
7724
|
+
[(b.x0 + b.x1) / 2, rootEnd],
|
|
7725
|
+
lockHangWeight(weight)
|
|
7726
|
+
)
|
|
6891
7727
|
);
|
|
6892
7728
|
break;
|
|
6893
7729
|
}
|
|
@@ -7129,7 +7965,9 @@ function declareParameters(roles) {
|
|
|
7129
7965
|
AddDeformer,
|
|
7130
7966
|
AddPart,
|
|
7131
7967
|
AddPhysicsRig,
|
|
7968
|
+
BakeError,
|
|
7132
7969
|
CaptureGridKeyform,
|
|
7970
|
+
CustomPartError,
|
|
7133
7971
|
DEFAULT_TURN_TARGETS,
|
|
7134
7972
|
DeleteDeformer,
|
|
7135
7973
|
DeletePart,
|
|
@@ -7185,6 +8023,7 @@ function declareParameters(roles) {
|
|
|
7185
8023
|
packAtlas,
|
|
7186
8024
|
parseLayerRoles,
|
|
7187
8025
|
partIdsOfRole,
|
|
8026
|
+
rigidResidual,
|
|
7188
8027
|
styleRemovedMessage,
|
|
7189
8028
|
upsertGridKeyform,
|
|
7190
8029
|
uvRectFor,
|