@ikijs/editor 0.30.1 → 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 +873 -38
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +870 -38
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2114,6 +2114,18 @@ var PatchError = class extends Error {
|
|
|
2114
2114
|
this.name = "PatchError";
|
|
2115
2115
|
}
|
|
2116
2116
|
};
|
|
2117
|
+
var CustomPartError = class extends Error {
|
|
2118
|
+
constructor(message) {
|
|
2119
|
+
super(message);
|
|
2120
|
+
this.name = "CustomPartError";
|
|
2121
|
+
}
|
|
2122
|
+
};
|
|
2123
|
+
var BakeError = class extends Error {
|
|
2124
|
+
constructor(message) {
|
|
2125
|
+
super(message);
|
|
2126
|
+
this.name = "BakeError";
|
|
2127
|
+
}
|
|
2128
|
+
};
|
|
2117
2129
|
|
|
2118
2130
|
// src/auto-rig/layout.ts
|
|
2119
2131
|
function boxOfLayer(layer) {
|
|
@@ -2522,10 +2534,10 @@ function checkSidesHaveFront(roles) {
|
|
|
2522
2534
|
function checkMouth(roles) {
|
|
2523
2535
|
const has = new Set(roles);
|
|
2524
2536
|
const present = LIP_ROLES.filter((r) => has.has(r));
|
|
2525
|
-
const
|
|
2526
|
-
if (
|
|
2537
|
+
const inside2 = LIP_INSIDE_ROLES.filter((r) => has.has(r));
|
|
2538
|
+
if (inside2.length > 0 && present.length === 0) {
|
|
2527
2539
|
throw new Error(
|
|
2528
|
-
`auto-rig: ${
|
|
2540
|
+
`auto-rig: ${inside2[0]} needs the lip set ${LIP_ROLES.join(", ")} (it is cut from the interior and clipped to it)`
|
|
2529
2541
|
);
|
|
2530
2542
|
}
|
|
2531
2543
|
if (present.length === 0) {
|
|
@@ -2745,10 +2757,10 @@ function mouthFold(role, opening) {
|
|
|
2745
2757
|
const stackThin = (c, y) => y > c.Tu ? c.Tu + UPPER_THIN * (y - c.Tu) : y;
|
|
2746
2758
|
return (col, y, v) => {
|
|
2747
2759
|
const k = 1 - v;
|
|
2748
|
-
const
|
|
2760
|
+
const inside2 = col >= x0 && col <= x1;
|
|
2749
2761
|
switch (role) {
|
|
2750
2762
|
case "lip_lower": {
|
|
2751
|
-
if (!
|
|
2763
|
+
if (!inside2) return [0, 0];
|
|
2752
2764
|
const c = opening.at(col);
|
|
2753
2765
|
return [0, (c.seam + c.overlap - c.Bl) * k];
|
|
2754
2766
|
}
|
|
@@ -2772,9 +2784,9 @@ function mouthFold(role, opening) {
|
|
|
2772
2784
|
line = opening.line(c += col < x0 ? 1 : -1);
|
|
2773
2785
|
const thin = line.bottom + UPPER_THIN * (y - line.bottom);
|
|
2774
2786
|
const mid = line.bottom + UPPER_THIN * (line.top - line.bottom) / 2;
|
|
2775
|
-
const travel =
|
|
2787
|
+
const travel = inside2 ? opening.at(col).seam - line.bottom : 0;
|
|
2776
2788
|
const shut = mid + line.squash * (thin - mid) + travel;
|
|
2777
|
-
const pull =
|
|
2789
|
+
const pull = inside2 ? 0 : (1 - FLICK_PULL) * ((col < x0 ? x0 : x1 + 1) - col);
|
|
2778
2790
|
return [pull * k, thin - y + (shut - thin) * k];
|
|
2779
2791
|
}
|
|
2780
2792
|
}
|
|
@@ -4107,14 +4119,14 @@ function cuesAt(ctx, land2, ax) {
|
|
|
4107
4119
|
const l0 = land2(L.role, L.lo, L.y, ax);
|
|
4108
4120
|
const l1 = land2(L.role, L.hi, L.y, ax);
|
|
4109
4121
|
const r0 = land2(R.role, R.lo, R.y, ax);
|
|
4110
|
-
const
|
|
4122
|
+
const r12 = land2(R.role, R.hi, R.y, ax);
|
|
4111
4123
|
const wl = l1 - l0;
|
|
4112
|
-
const wr =
|
|
4124
|
+
const wr = r12 - r0;
|
|
4113
4125
|
const wl0 = L.hi - L.lo;
|
|
4114
4126
|
const wr0 = R.hi - R.lo;
|
|
4115
4127
|
const ratio = ax < 0 ? wl / wr / (wl0 / wr0) : wr / wl / (wr0 / wl0);
|
|
4116
4128
|
const pc0 = (L.lo + L.hi + R.lo + R.hi) / 4;
|
|
4117
|
-
const pc = (l0 + l1 + r0 +
|
|
4129
|
+
const pc = (l0 + l1 + r0 + r12) / 4;
|
|
4118
4130
|
const y = ctx.frame.eyeY;
|
|
4119
4131
|
const hl0 = Math.min(...ctx.candidates.left.map((c) => c.x));
|
|
4120
4132
|
const hr0 = Math.max(...ctx.candidates.right.map((c) => c.x));
|
|
@@ -4752,7 +4764,7 @@ function blendStops(values, offsets, v) {
|
|
|
4752
4764
|
const t = (x - values[i]) / (values[i + 1] - values[i]);
|
|
4753
4765
|
return offsets[i].map((o, k) => o + (offsets[i + 1][k] - o) * t);
|
|
4754
4766
|
}
|
|
4755
|
-
function
|
|
4767
|
+
function bindToRest(l, x, y) {
|
|
4756
4768
|
const { cols, rows, points } = l;
|
|
4757
4769
|
const stride = cols + 1;
|
|
4758
4770
|
let col = cols - 1;
|
|
@@ -4773,8 +4785,17 @@ function land(l, offsets, x, y) {
|
|
|
4773
4785
|
const x1 = points[(col + 1) * 2];
|
|
4774
4786
|
const yT = points[row * stride * 2 + 1];
|
|
4775
4787
|
const yB = points[(row + 1) * stride * 2 + 1];
|
|
4776
|
-
|
|
4777
|
-
|
|
4788
|
+
return {
|
|
4789
|
+
row,
|
|
4790
|
+
col,
|
|
4791
|
+
s: clamp((x - x0) / (x1 - x0), 0, 1),
|
|
4792
|
+
t: clamp((yT - y) / (yT - yB), 0, 1)
|
|
4793
|
+
};
|
|
4794
|
+
}
|
|
4795
|
+
function land(l, offsets, x, y) {
|
|
4796
|
+
const { cols, points } = l;
|
|
4797
|
+
const stride = cols + 1;
|
|
4798
|
+
const { row, col, s, t } = bindToRest(l, x, y);
|
|
4778
4799
|
const at = (r, c) => {
|
|
4779
4800
|
const k = (r * stride + c) * 2;
|
|
4780
4801
|
return [points[k] + offsets[k], points[k + 1] + offsets[k + 1]];
|
|
@@ -5114,7 +5135,7 @@ function armGeometry(layer, bodyAxisX) {
|
|
|
5114
5135
|
b.x1 - elbow.x,
|
|
5115
5136
|
b.y1 - elbow.y
|
|
5116
5137
|
);
|
|
5117
|
-
const
|
|
5138
|
+
const inside2 = (x, y) => {
|
|
5118
5139
|
const k = Math.floor(y) - bbox.y;
|
|
5119
5140
|
if (k < 0 || k >= bbox.h) return false;
|
|
5120
5141
|
const [rs, re] = span3(k);
|
|
@@ -5123,7 +5144,7 @@ function armGeometry(layer, bodyAxisX) {
|
|
|
5123
5144
|
const capRays = Array.from({ length: CAP_SEGMENTS + 1 }, (_, i) => {
|
|
5124
5145
|
const a = Math.PI * i / CAP_SEGMENTS;
|
|
5125
5146
|
let t = 1;
|
|
5126
|
-
while (t <= capRadius + CAP_INSET &&
|
|
5147
|
+
while (t <= capRadius + CAP_INSET && inside2(
|
|
5127
5148
|
elbow.x + canvasW / 2 + t * Math.cos(a),
|
|
5128
5149
|
canvasH / 2 - elbow.y - t * Math.sin(a)
|
|
5129
5150
|
)) {
|
|
@@ -5325,6 +5346,675 @@ function forearmPoseDeformer(role, arm) {
|
|
|
5325
5346
|
};
|
|
5326
5347
|
}
|
|
5327
5348
|
|
|
5349
|
+
// src/auto-rig/chain.ts
|
|
5350
|
+
function multiply(a, b) {
|
|
5351
|
+
return [
|
|
5352
|
+
a[0] * b[0] + a[2] * b[1],
|
|
5353
|
+
a[1] * b[0] + a[3] * b[1],
|
|
5354
|
+
a[0] * b[2] + a[2] * b[3],
|
|
5355
|
+
a[1] * b[2] + a[3] * b[3],
|
|
5356
|
+
a[0] * b[4] + a[2] * b[5] + a[4],
|
|
5357
|
+
a[1] * b[4] + a[3] * b[5] + a[5]
|
|
5358
|
+
];
|
|
5359
|
+
}
|
|
5360
|
+
function valueOf(parameters, values, id) {
|
|
5361
|
+
const p = parameters.find((q) => q.id === id);
|
|
5362
|
+
if (p === void 0) return 0;
|
|
5363
|
+
const v = values[id] ?? p.default;
|
|
5364
|
+
return Math.min(p.max, Math.max(p.min, v));
|
|
5365
|
+
}
|
|
5366
|
+
function ownMatrix(d, parameters, values) {
|
|
5367
|
+
let x = d.transform?.x ?? 0;
|
|
5368
|
+
let y = d.transform?.y ?? 0;
|
|
5369
|
+
let rotation = d.transform?.rotation ?? 0;
|
|
5370
|
+
let sx = d.transform?.scaleX ?? 1;
|
|
5371
|
+
let sy = d.transform?.scaleY ?? 1;
|
|
5372
|
+
for (const b of d.bindings ?? []) {
|
|
5373
|
+
const p = parameters.find((q) => q.id === b.parameter);
|
|
5374
|
+
const t = p === void 0 || p.max === p.min ? 0 : (valueOf(parameters, values, b.parameter) - p.min) / (p.max - p.min);
|
|
5375
|
+
const v = b.from + (b.to - b.from) * t;
|
|
5376
|
+
if (b.channel === "translateX") x += v;
|
|
5377
|
+
else if (b.channel === "translateY") y += v;
|
|
5378
|
+
else if (b.channel === "rotate") rotation += v;
|
|
5379
|
+
else if (b.channel === "scaleX") sx += v;
|
|
5380
|
+
else if (b.channel === "scaleY") sy += v;
|
|
5381
|
+
}
|
|
5382
|
+
const r = rotation * Math.PI / 180;
|
|
5383
|
+
const c = Math.cos(r);
|
|
5384
|
+
const s = Math.sin(r);
|
|
5385
|
+
const trs = [c * sx, s * sx, -s * sy, c * sy, x, y];
|
|
5386
|
+
return multiply(multiply([1, 0, 0, 1, d.pivot.x, d.pivot.y], trs), [
|
|
5387
|
+
1,
|
|
5388
|
+
0,
|
|
5389
|
+
0,
|
|
5390
|
+
1,
|
|
5391
|
+
-d.pivot.x,
|
|
5392
|
+
-d.pivot.y
|
|
5393
|
+
]);
|
|
5394
|
+
}
|
|
5395
|
+
function localGrid(w, parameters, values) {
|
|
5396
|
+
const points = [...w.grid.points];
|
|
5397
|
+
for (const warp of w.warps ?? []) {
|
|
5398
|
+
const offsets = blendStops(
|
|
5399
|
+
warp.keyforms.map((k) => k.value),
|
|
5400
|
+
warp.keyforms.map((k) => k.offsets),
|
|
5401
|
+
valueOf(parameters, values, warp.parameter)
|
|
5402
|
+
);
|
|
5403
|
+
for (let i = 0; i < points.length; i++) points[i] += offsets[i];
|
|
5404
|
+
}
|
|
5405
|
+
return points;
|
|
5406
|
+
}
|
|
5407
|
+
function riddenFrame(pivot, w, q) {
|
|
5408
|
+
const { cols, points: rest } = w.grid;
|
|
5409
|
+
const stride = cols + 1;
|
|
5410
|
+
const { row, col, s, t } = bindToRest(w.grid, pivot.x, pivot.y);
|
|
5411
|
+
const xl = rest[col * 2];
|
|
5412
|
+
const xr = rest[(col + 1) * 2];
|
|
5413
|
+
const yt = rest[row * stride * 2 + 1];
|
|
5414
|
+
const yb = rest[(row + 1) * stride * 2 + 1];
|
|
5415
|
+
const i00 = (row * stride + col) * 2;
|
|
5416
|
+
const i10 = (row * stride + col + 1) * 2;
|
|
5417
|
+
const i01 = ((row + 1) * stride + col) * 2;
|
|
5418
|
+
const i11 = ((row + 1) * stride + col + 1) * 2;
|
|
5419
|
+
const lerp = (a, b, k) => a + (b - a) * k;
|
|
5420
|
+
const topX = lerp(q[i00], q[i10], s);
|
|
5421
|
+
const topY = lerp(q[i00 + 1], q[i10 + 1], s);
|
|
5422
|
+
const botX = lerp(q[i01], q[i11], s);
|
|
5423
|
+
const botY = lerp(q[i01 + 1], q[i11 + 1], s);
|
|
5424
|
+
const px = lerp(topX, botX, t);
|
|
5425
|
+
const py = lerp(topY, botY, t);
|
|
5426
|
+
const width = xr - xl;
|
|
5427
|
+
const height = yt - yb;
|
|
5428
|
+
const exX = lerp(q[i10] - q[i00], q[i11] - q[i01], t) / width;
|
|
5429
|
+
const exY = lerp(q[i10 + 1] - q[i00 + 1], q[i11 + 1] - q[i01 + 1], t) / width;
|
|
5430
|
+
const eyX = -lerp(q[i01] - q[i00], q[i11] - q[i10], s) / height;
|
|
5431
|
+
const eyY = -lerp(q[i01 + 1] - q[i00 + 1], q[i11 + 1] - q[i10 + 1], s) / height;
|
|
5432
|
+
const theta = Math.atan2(exY - eyX, exX + eyY);
|
|
5433
|
+
const c = Math.cos(theta);
|
|
5434
|
+
const sn = Math.sin(theta);
|
|
5435
|
+
return [
|
|
5436
|
+
c,
|
|
5437
|
+
sn,
|
|
5438
|
+
-sn,
|
|
5439
|
+
c,
|
|
5440
|
+
px - (c * pivot.x - sn * pivot.y),
|
|
5441
|
+
py - (sn * pivot.x + c * pivot.y)
|
|
5442
|
+
];
|
|
5443
|
+
}
|
|
5444
|
+
function matrixWorldAt(deformers2, parameters, id, values = {}) {
|
|
5445
|
+
for (const key of Object.keys(values)) {
|
|
5446
|
+
if (!parameters.some((q) => q.id === key)) {
|
|
5447
|
+
throw new Error(`chain: unknown parameter "${key}"`);
|
|
5448
|
+
}
|
|
5449
|
+
}
|
|
5450
|
+
const d = deformers2.find((x) => x.id === id);
|
|
5451
|
+
if (d === void 0) throw new Error(`chain: no deformer "${id}"`);
|
|
5452
|
+
if (d.kind === "warp") {
|
|
5453
|
+
throw new Error(`chain: "${id}" is a warp deformer, not a matrix`);
|
|
5454
|
+
}
|
|
5455
|
+
const own = ownMatrix(d, parameters, values);
|
|
5456
|
+
if (d.parent === void 0) return own;
|
|
5457
|
+
const parent = deformers2.find((x) => x.id === d.parent);
|
|
5458
|
+
if (parent === void 0) {
|
|
5459
|
+
throw new Error(`chain: "${id}" has an unknown parent "${d.parent}"`);
|
|
5460
|
+
}
|
|
5461
|
+
if (parent.kind !== "warp") {
|
|
5462
|
+
throw new Error(
|
|
5463
|
+
`chain: "${id}" hangs from the matrix deformer "${parent.id}"; only a root warp is supported`
|
|
5464
|
+
);
|
|
5465
|
+
}
|
|
5466
|
+
if (parent.parent !== void 0) {
|
|
5467
|
+
throw new Error(
|
|
5468
|
+
`chain: "${id}" hangs from warp "${parent.id}", which has a parent "${parent.parent}"; only a root warp is supported`
|
|
5469
|
+
);
|
|
5470
|
+
}
|
|
5471
|
+
if (parent.warp2d !== void 0) {
|
|
5472
|
+
throw new Error(
|
|
5473
|
+
`chain: "${id}" hangs from warp "${parent.id}", which carries a warp2d; only 1D warps are supported`
|
|
5474
|
+
);
|
|
5475
|
+
}
|
|
5476
|
+
return multiply(
|
|
5477
|
+
riddenFrame(d.pivot, parent, localGrid(parent, parameters, values)),
|
|
5478
|
+
own
|
|
5479
|
+
);
|
|
5480
|
+
}
|
|
5481
|
+
|
|
5482
|
+
// src/auto-rig/bakes.ts
|
|
5483
|
+
var TARGET_KEYS = [
|
|
5484
|
+
"id",
|
|
5485
|
+
"param",
|
|
5486
|
+
"parts",
|
|
5487
|
+
"box",
|
|
5488
|
+
"grid",
|
|
5489
|
+
"stops",
|
|
5490
|
+
"mirror",
|
|
5491
|
+
"why"
|
|
5492
|
+
];
|
|
5493
|
+
var BOX_KEYS = ["x", "y", "w", "h"];
|
|
5494
|
+
var MAX_VERTICES = 65536;
|
|
5495
|
+
var CELL_PX = [4, 400];
|
|
5496
|
+
var MAX_MESH_COLS = 64;
|
|
5497
|
+
var MAX_MESH_ROWS = 400;
|
|
5498
|
+
var DEFAULT_BOX_MARGIN = 2;
|
|
5499
|
+
var SAME_STOP = 1e-9;
|
|
5500
|
+
var PLAIN_DECIMAL = /^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/;
|
|
5501
|
+
var finite = (v) => typeof v === "number" && Number.isFinite(v);
|
|
5502
|
+
var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
5503
|
+
var show = (v) => typeof v === "number" ? String(v) : JSON.stringify(v) ?? String(v);
|
|
5504
|
+
function mirrorLattice(lattice2, cols, rows) {
|
|
5505
|
+
const out = new Array(lattice2.length);
|
|
5506
|
+
for (let r = 0; r <= rows; r++) {
|
|
5507
|
+
for (let c = 0; c <= cols; c++) {
|
|
5508
|
+
const [dx, dy] = lattice2[r * (cols + 1) + (cols - c)];
|
|
5509
|
+
out[r * (cols + 1) + c] = [-dx, dy];
|
|
5510
|
+
}
|
|
5511
|
+
}
|
|
5512
|
+
return out;
|
|
5513
|
+
}
|
|
5514
|
+
var r1 = (v) => Math.round(v * 10) / 10;
|
|
5515
|
+
var findStop = (stops, v) => [...stops.keys()].find((k) => Math.abs(k - v) <= SAME_STOP);
|
|
5516
|
+
function checkGrid(at, grid) {
|
|
5517
|
+
if (!isObject(grid)) {
|
|
5518
|
+
throw new BakeError(`${at}.grid must be { cols, rows }, got ${show(grid)}`);
|
|
5519
|
+
}
|
|
5520
|
+
checkedKeys(`${at}.grid`, grid, ["cols", "rows"], BakeError);
|
|
5521
|
+
const out = { cols: 0, rows: 0 };
|
|
5522
|
+
for (const key of ["cols", "rows"]) {
|
|
5523
|
+
const v = checkedNumber(
|
|
5524
|
+
`${at}.grid.${key}`,
|
|
5525
|
+
grid[key],
|
|
5526
|
+
1,
|
|
5527
|
+
32,
|
|
5528
|
+
false,
|
|
5529
|
+
BakeError
|
|
5530
|
+
);
|
|
5531
|
+
if (!Number.isInteger(v)) {
|
|
5532
|
+
throw new BakeError(`${at}.grid.${key} must be an integer, got ${v}`);
|
|
5533
|
+
}
|
|
5534
|
+
out[key] = v;
|
|
5535
|
+
}
|
|
5536
|
+
return out;
|
|
5537
|
+
}
|
|
5538
|
+
function checkBox(at, box, partBoxes) {
|
|
5539
|
+
if (box === void 0) {
|
|
5540
|
+
const x0 = Math.min(...partBoxes.map((b) => b.x)) - DEFAULT_BOX_MARGIN;
|
|
5541
|
+
const y0 = Math.min(...partBoxes.map((b) => b.y)) - DEFAULT_BOX_MARGIN;
|
|
5542
|
+
const x1 = Math.max(...partBoxes.map((b) => b.x + b.w)) + DEFAULT_BOX_MARGIN;
|
|
5543
|
+
const y1 = Math.max(...partBoxes.map((b) => b.y + b.h)) + DEFAULT_BOX_MARGIN;
|
|
5544
|
+
return { x: x0, y: y0, w: x1 - x0, h: y1 - y0 };
|
|
5545
|
+
}
|
|
5546
|
+
if (!isObject(box)) {
|
|
5547
|
+
throw new BakeError(`${at}.box must be { x, y, w, h }, got ${show(box)}`);
|
|
5548
|
+
}
|
|
5549
|
+
checkedKeys(`${at}.box`, box, BOX_KEYS, BakeError);
|
|
5550
|
+
const [x, y] = ["x", "y"].map(
|
|
5551
|
+
(k) => checkedNumber(
|
|
5552
|
+
`${at}.box.${k}`,
|
|
5553
|
+
box[k],
|
|
5554
|
+
-Infinity,
|
|
5555
|
+
Infinity,
|
|
5556
|
+
false,
|
|
5557
|
+
BakeError
|
|
5558
|
+
)
|
|
5559
|
+
);
|
|
5560
|
+
const [w, h] = ["w", "h"].map(
|
|
5561
|
+
(k) => checkedNumber(`${at}.box.${k}`, box[k], 0, Infinity, true, BakeError)
|
|
5562
|
+
);
|
|
5563
|
+
return { x, y, w, h };
|
|
5564
|
+
}
|
|
5565
|
+
function checkStops(at, t, info, rest, cols, rows) {
|
|
5566
|
+
if (!isObject(t.stops)) {
|
|
5567
|
+
throw new BakeError(
|
|
5568
|
+
`${at}.stops must be an object keyed by a ${info.id} value, got ${show(t.stops)}`
|
|
5569
|
+
);
|
|
5570
|
+
}
|
|
5571
|
+
const n = (cols + 1) * (rows + 1);
|
|
5572
|
+
const stops = /* @__PURE__ */ new Map();
|
|
5573
|
+
for (const [key, list] of Object.entries(t.stops)) {
|
|
5574
|
+
const sat = `${at}.stops["${key}"]`;
|
|
5575
|
+
if (!PLAIN_DECIMAL.test(key)) {
|
|
5576
|
+
throw new BakeError(`${sat} must be keyed by a plain decimal number`);
|
|
5577
|
+
}
|
|
5578
|
+
const v = Number(key);
|
|
5579
|
+
if (!Number.isFinite(v) || v < info.min || v > info.max) {
|
|
5580
|
+
throw new BakeError(
|
|
5581
|
+
`${sat} is outside ${info.id}'s range ${info.min} to ${info.max}`
|
|
5582
|
+
);
|
|
5583
|
+
}
|
|
5584
|
+
if (Math.abs(v - rest) <= SAME_STOP) {
|
|
5585
|
+
throw new BakeError(
|
|
5586
|
+
`${sat} is ${info.id}'s rest value ${rest}: rest is implicit, leave it out`
|
|
5587
|
+
);
|
|
5588
|
+
}
|
|
5589
|
+
if (findStop(stops, v) !== void 0) {
|
|
5590
|
+
throw new BakeError(`${sat} repeats an earlier stop`);
|
|
5591
|
+
}
|
|
5592
|
+
if (!Array.isArray(list) || list.length !== n) {
|
|
5593
|
+
throw new BakeError(
|
|
5594
|
+
`${sat} must be ${n} points [dx, dy] (${cols + 1} x ${rows + 1}), got ${Array.isArray(list) ? `${list.length} points` : show(list)}`
|
|
5595
|
+
);
|
|
5596
|
+
}
|
|
5597
|
+
list.forEach((pt, k) => {
|
|
5598
|
+
if (!Array.isArray(pt) || pt.length !== 2) {
|
|
5599
|
+
throw new BakeError(`${sat}[${k}] must be [dx, dy], got ${show(pt)}`);
|
|
5600
|
+
}
|
|
5601
|
+
pt.forEach((c, j) => {
|
|
5602
|
+
if (!finite(c)) {
|
|
5603
|
+
throw new BakeError(
|
|
5604
|
+
`${sat}[${k}][${j}] must be a finite number, got ${String(c)}`
|
|
5605
|
+
);
|
|
5606
|
+
}
|
|
5607
|
+
});
|
|
5608
|
+
});
|
|
5609
|
+
stops.set(v, list);
|
|
5610
|
+
}
|
|
5611
|
+
if (stops.size === 0) {
|
|
5612
|
+
throw new BakeError(`${at}.stops must have at least one stop`);
|
|
5613
|
+
}
|
|
5614
|
+
if (t.mirror === true) {
|
|
5615
|
+
for (const [v, lattice2] of [...stops]) {
|
|
5616
|
+
const m = 2 * rest - v;
|
|
5617
|
+
if (m < info.min - SAME_STOP || m > info.max + SAME_STOP) continue;
|
|
5618
|
+
if (Math.abs(m - rest) <= SAME_STOP || findStop(stops, m) !== void 0) {
|
|
5619
|
+
continue;
|
|
5620
|
+
}
|
|
5621
|
+
stops.set(
|
|
5622
|
+
Math.min(info.max, Math.max(info.min, m)),
|
|
5623
|
+
mirrorLattice(lattice2, cols, rows)
|
|
5624
|
+
);
|
|
5625
|
+
}
|
|
5626
|
+
}
|
|
5627
|
+
return stops;
|
|
5628
|
+
}
|
|
5629
|
+
function checkTarget(t, i, customParts, paramById) {
|
|
5630
|
+
const at = `bakes.targets[${i}]`;
|
|
5631
|
+
if (!isObject(t)) {
|
|
5632
|
+
throw new BakeError(`${at} must be a plain object, got ${show(t)}`);
|
|
5633
|
+
}
|
|
5634
|
+
checkedKeys(at, t, TARGET_KEYS, BakeError);
|
|
5635
|
+
if (t.id !== void 0 && typeof t.id !== "string") {
|
|
5636
|
+
throw new BakeError(`${at}.id must be a string, got ${show(t.id)}`);
|
|
5637
|
+
}
|
|
5638
|
+
if (t.why !== void 0 && typeof t.why !== "string") {
|
|
5639
|
+
throw new BakeError(`${at}.why must be a string, got ${show(t.why)}`);
|
|
5640
|
+
}
|
|
5641
|
+
const info = typeof t.param === "string" ? paramById.get(t.param) : void 0;
|
|
5642
|
+
if (info === void 0) {
|
|
5643
|
+
throw new BakeError(
|
|
5644
|
+
`${at}.param must be a parameter the model declares, got ${show(t.param)}`
|
|
5645
|
+
);
|
|
5646
|
+
}
|
|
5647
|
+
const rest = Math.min(info.max, Math.max(info.min, info.default));
|
|
5648
|
+
const customIds = customParts.map((c) => c.id);
|
|
5649
|
+
if (!Array.isArray(t.parts) || t.parts.length === 0) {
|
|
5650
|
+
throw new BakeError(
|
|
5651
|
+
`${at}.parts must be a non-empty array of part ids, got ${show(t.parts)}`
|
|
5652
|
+
);
|
|
5653
|
+
}
|
|
5654
|
+
const parts = t.parts;
|
|
5655
|
+
parts.forEach((id, k) => {
|
|
5656
|
+
if (typeof id !== "string" || !customIds.includes(id)) {
|
|
5657
|
+
throw new BakeError(
|
|
5658
|
+
`${at}.parts[${k}] must be a custom part id (${customIds.join(", ")}): a bake moves custom parts only, got ${show(id)}`
|
|
5659
|
+
);
|
|
5660
|
+
}
|
|
5661
|
+
if (parts.indexOf(id) !== k) {
|
|
5662
|
+
throw new BakeError(`${at}.parts[${k}] repeats "${id}"`);
|
|
5663
|
+
}
|
|
5664
|
+
});
|
|
5665
|
+
const { cols, rows } = checkGrid(at, t.grid);
|
|
5666
|
+
const partBoxes = parts.map(
|
|
5667
|
+
(id) => customParts.find((c) => c.id === id).layer.bbox
|
|
5668
|
+
);
|
|
5669
|
+
const box = checkBox(at, t.box, partBoxes);
|
|
5670
|
+
if (t.mirror !== void 0 && typeof t.mirror !== "boolean") {
|
|
5671
|
+
throw new BakeError(
|
|
5672
|
+
`${at}.mirror must be true or false, got ${show(t.mirror)}`
|
|
5673
|
+
);
|
|
5674
|
+
}
|
|
5675
|
+
const stops = checkStops(at, t, info, rest, cols, rows);
|
|
5676
|
+
return {
|
|
5677
|
+
id: typeof t.id === "string" ? t.id : `target${i}`,
|
|
5678
|
+
param: info.id,
|
|
5679
|
+
rest,
|
|
5680
|
+
parts,
|
|
5681
|
+
box,
|
|
5682
|
+
cols,
|
|
5683
|
+
rows,
|
|
5684
|
+
stops
|
|
5685
|
+
};
|
|
5686
|
+
}
|
|
5687
|
+
function checkMesh(at, choice, box) {
|
|
5688
|
+
if (!isObject(choice)) {
|
|
5689
|
+
throw new BakeError(
|
|
5690
|
+
`${at} must be { rows, cols } or { cellPx }, got ${show(choice)}`
|
|
5691
|
+
);
|
|
5692
|
+
}
|
|
5693
|
+
checkedKeys(at, choice, ["rows", "cols", "cellPx"], BakeError);
|
|
5694
|
+
if (choice.cellPx !== void 0) {
|
|
5695
|
+
if (choice.rows !== void 0 || choice.cols !== void 0) {
|
|
5696
|
+
throw new BakeError(`${at} takes { cellPx } or { rows, cols }, not both`);
|
|
5697
|
+
}
|
|
5698
|
+
const cell = checkedNumber(
|
|
5699
|
+
`${at}.cellPx`,
|
|
5700
|
+
choice.cellPx,
|
|
5701
|
+
CELL_PX[0],
|
|
5702
|
+
CELL_PX[1],
|
|
5703
|
+
false,
|
|
5704
|
+
BakeError
|
|
5705
|
+
);
|
|
5706
|
+
const count = (Math.max(1, Math.round(box.w / cell)) + 1) * (Math.max(1, Math.round(box.h / cell)) + 1);
|
|
5707
|
+
if (count > MAX_VERTICES) {
|
|
5708
|
+
throw new BakeError(
|
|
5709
|
+
`${at}.cellPx ${cell} makes ${count} vertices, over the format's ${MAX_VERTICES} limit`
|
|
5710
|
+
);
|
|
5711
|
+
}
|
|
5712
|
+
return { cellPx: cell };
|
|
5713
|
+
}
|
|
5714
|
+
const cols = checkedNumber(
|
|
5715
|
+
`${at}.cols`,
|
|
5716
|
+
choice.cols,
|
|
5717
|
+
1,
|
|
5718
|
+
MAX_MESH_COLS,
|
|
5719
|
+
false,
|
|
5720
|
+
BakeError
|
|
5721
|
+
);
|
|
5722
|
+
if (!Number.isInteger(cols)) {
|
|
5723
|
+
throw new BakeError(`${at}.cols must be an integer, got ${cols}`);
|
|
5724
|
+
}
|
|
5725
|
+
const ys = choice.rows;
|
|
5726
|
+
if (!Array.isArray(ys) || ys.length < 2 || ys.length > MAX_MESH_ROWS) {
|
|
5727
|
+
throw new BakeError(
|
|
5728
|
+
`${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)}`
|
|
5729
|
+
);
|
|
5730
|
+
}
|
|
5731
|
+
ys.forEach((y, k) => {
|
|
5732
|
+
if (!finite(y)) {
|
|
5733
|
+
throw new BakeError(
|
|
5734
|
+
`${at}.rows[${k}] must be a finite number, got ${String(y)}`
|
|
5735
|
+
);
|
|
5736
|
+
}
|
|
5737
|
+
if (k > 0 && !(y > ys[k - 1])) {
|
|
5738
|
+
throw new BakeError(
|
|
5739
|
+
`${at}.rows[${k}] must be below the row before it (rows are strictly increasing), got ${y} after ${String(ys[k - 1])}`
|
|
5740
|
+
);
|
|
5741
|
+
}
|
|
5742
|
+
});
|
|
5743
|
+
const last = ys.length - 1;
|
|
5744
|
+
if (Math.abs(ys[0] - box.y) > 0.5) {
|
|
5745
|
+
throw new BakeError(
|
|
5746
|
+
`${at}.rows[0] must be the part's box top ${r1(box.y)} (within 0.5 px), got ${String(ys[0])}`
|
|
5747
|
+
);
|
|
5748
|
+
}
|
|
5749
|
+
if (Math.abs(ys[last] - (box.y + box.h)) > 0.5) {
|
|
5750
|
+
throw new BakeError(
|
|
5751
|
+
`${at}.rows[${last}] must be the part's box bottom ${r1(box.y + box.h)} (within 0.5 px), got ${String(ys[last])}`
|
|
5752
|
+
);
|
|
5753
|
+
}
|
|
5754
|
+
return { rows: ys, cols };
|
|
5755
|
+
}
|
|
5756
|
+
function parseBakes(bakes, customParts, parameters) {
|
|
5757
|
+
if (customParts === void 0 || customParts.length === 0) {
|
|
5758
|
+
throw new BakeError(
|
|
5759
|
+
"bakes needs customParts: a bake moves custom parts only, and none were given"
|
|
5760
|
+
);
|
|
5761
|
+
}
|
|
5762
|
+
if (!isObject(bakes)) {
|
|
5763
|
+
throw new BakeError(`bakes must be a plain object, got ${show(bakes)}`);
|
|
5764
|
+
}
|
|
5765
|
+
checkedKeys("bakes", bakes, ["targets", "meshes"], BakeError);
|
|
5766
|
+
const { targets, meshes } = bakes;
|
|
5767
|
+
if (!Array.isArray(targets) || targets.length === 0) {
|
|
5768
|
+
throw new BakeError("bakes.targets must be a non-empty array");
|
|
5769
|
+
}
|
|
5770
|
+
const paramById = new Map(parameters.map((p) => [p.id, p]));
|
|
5771
|
+
const parsedTargets = targets.map(
|
|
5772
|
+
(t, i) => checkTarget(t, i, customParts, paramById)
|
|
5773
|
+
);
|
|
5774
|
+
const parsedMeshes = /* @__PURE__ */ new Map();
|
|
5775
|
+
if (meshes !== void 0) {
|
|
5776
|
+
if (!isObject(meshes)) {
|
|
5777
|
+
throw new BakeError(
|
|
5778
|
+
`bakes.meshes must be an object keyed by part id, got ${show(meshes)}`
|
|
5779
|
+
);
|
|
5780
|
+
}
|
|
5781
|
+
for (const [id, choice] of Object.entries(meshes)) {
|
|
5782
|
+
const at = `bakes.meshes.${id}`;
|
|
5783
|
+
const part = customParts.find((c) => c.id === id);
|
|
5784
|
+
if (part === void 0) {
|
|
5785
|
+
throw new BakeError(
|
|
5786
|
+
`${at}: "${id}" is not a custom part (${customParts.map((c) => c.id).join(", ")}): only a custom part is re-meshed`
|
|
5787
|
+
);
|
|
5788
|
+
}
|
|
5789
|
+
parsedMeshes.set(id, checkMesh(at, choice, part.layer.bbox));
|
|
5790
|
+
}
|
|
5791
|
+
}
|
|
5792
|
+
return { targets: parsedTargets, meshes: parsedMeshes };
|
|
5793
|
+
}
|
|
5794
|
+
var OFFSET_STEP = 1e-6;
|
|
5795
|
+
var RIGID_BEND = 25e-4;
|
|
5796
|
+
function rigidResidual(rest, moved) {
|
|
5797
|
+
const n = rest.length;
|
|
5798
|
+
if (moved.length !== n) {
|
|
5799
|
+
throw new Error(
|
|
5800
|
+
`rigidResidual: ${n} rest points but ${moved.length} moved points`
|
|
5801
|
+
);
|
|
5802
|
+
}
|
|
5803
|
+
if (n === 0) return 0;
|
|
5804
|
+
let pcx = 0;
|
|
5805
|
+
let pcy = 0;
|
|
5806
|
+
let qcx = 0;
|
|
5807
|
+
let qcy = 0;
|
|
5808
|
+
for (let i = 0; i < n; i++) {
|
|
5809
|
+
pcx += rest[i][0];
|
|
5810
|
+
pcy += rest[i][1];
|
|
5811
|
+
qcx += moved[i][0];
|
|
5812
|
+
qcy += moved[i][1];
|
|
5813
|
+
}
|
|
5814
|
+
pcx /= n;
|
|
5815
|
+
pcy /= n;
|
|
5816
|
+
qcx /= n;
|
|
5817
|
+
qcy /= n;
|
|
5818
|
+
let dot = 0;
|
|
5819
|
+
let cross = 0;
|
|
5820
|
+
for (let i = 0; i < n; i++) {
|
|
5821
|
+
const px = rest[i][0] - pcx;
|
|
5822
|
+
const py = rest[i][1] - pcy;
|
|
5823
|
+
const qx = moved[i][0] - qcx;
|
|
5824
|
+
const qy = moved[i][1] - qcy;
|
|
5825
|
+
dot += px * qx + py * qy;
|
|
5826
|
+
cross += px * qy - py * qx;
|
|
5827
|
+
}
|
|
5828
|
+
const theta = Math.atan2(cross, dot);
|
|
5829
|
+
const c = Math.cos(theta);
|
|
5830
|
+
const s = Math.sin(theta);
|
|
5831
|
+
let worst = 0;
|
|
5832
|
+
for (let i = 0; i < n; i++) {
|
|
5833
|
+
const px = rest[i][0] - pcx;
|
|
5834
|
+
const py = rest[i][1] - pcy;
|
|
5835
|
+
const ex = qcx + c * px - s * py - moved[i][0];
|
|
5836
|
+
const ey = qcy + s * px + c * py - moved[i][1];
|
|
5837
|
+
worst = Math.max(worst, Math.hypot(ex, ey));
|
|
5838
|
+
}
|
|
5839
|
+
return worst;
|
|
5840
|
+
}
|
|
5841
|
+
function latticeAt(t, lattice2, x, y) {
|
|
5842
|
+
const { cols, rows, box } = t;
|
|
5843
|
+
const u = clamp((x - box.x) / box.w, 0, 1) * cols;
|
|
5844
|
+
const v = clamp((y - box.y) / box.h, 0, 1) * rows;
|
|
5845
|
+
const c = Math.min(Math.floor(u), cols - 1);
|
|
5846
|
+
const r = Math.min(Math.floor(v), rows - 1);
|
|
5847
|
+
const fx = u - c;
|
|
5848
|
+
const fy = v - r;
|
|
5849
|
+
const at = (rr, cc) => lattice2[rr * (cols + 1) + cc];
|
|
5850
|
+
const mix = (k) => {
|
|
5851
|
+
const top = at(r, c)[k] + (at(r, c + 1)[k] - at(r, c)[k]) * fx;
|
|
5852
|
+
const bot = at(r + 1, c)[k] + (at(r + 1, c + 1)[k] - at(r + 1, c)[k]) * fx;
|
|
5853
|
+
return top + (bot - top) * fy;
|
|
5854
|
+
};
|
|
5855
|
+
return [mix(0), mix(1)];
|
|
5856
|
+
}
|
|
5857
|
+
function targetField(t, v) {
|
|
5858
|
+
const side = Math.sign(v - t.rest);
|
|
5859
|
+
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));
|
|
5860
|
+
const a = Math.abs(v - t.rest);
|
|
5861
|
+
return (x, y) => {
|
|
5862
|
+
if (knots.length === 0) return [0, 0];
|
|
5863
|
+
const at = (k) => latticeAt(t, t.stops.get(k), x, y);
|
|
5864
|
+
let lo = 0;
|
|
5865
|
+
let loVal = [0, 0];
|
|
5866
|
+
for (const k of knots) {
|
|
5867
|
+
const hi = Math.abs(k - t.rest);
|
|
5868
|
+
const hiVal = at(k);
|
|
5869
|
+
if (a <= hi + SAME_STOP) {
|
|
5870
|
+
const f = hi - lo > 0 ? Math.min(1, (a - lo) / (hi - lo)) : 1;
|
|
5871
|
+
return [
|
|
5872
|
+
loVal[0] + (hiVal[0] - loVal[0]) * f,
|
|
5873
|
+
loVal[1] + (hiVal[1] - loVal[1]) * f
|
|
5874
|
+
];
|
|
5875
|
+
}
|
|
5876
|
+
lo = hi;
|
|
5877
|
+
loVal = hiVal;
|
|
5878
|
+
}
|
|
5879
|
+
return loVal;
|
|
5880
|
+
};
|
|
5881
|
+
}
|
|
5882
|
+
var apply = (m, x, y) => [
|
|
5883
|
+
m[0] * x + m[2] * y + m[4],
|
|
5884
|
+
m[1] * x + m[3] * y + m[5]
|
|
5885
|
+
];
|
|
5886
|
+
function invert(m) {
|
|
5887
|
+
const det = m[0] * m[3] - m[1] * m[2];
|
|
5888
|
+
const a = m[3] / det;
|
|
5889
|
+
const b = -m[1] / det;
|
|
5890
|
+
const c = -m[2] / det;
|
|
5891
|
+
const d = m[0] / det;
|
|
5892
|
+
return [a, b, c, d, -(a * m[4] + c * m[5]), -(b * m[4] + d * m[5])];
|
|
5893
|
+
}
|
|
5894
|
+
function remesh(part, entry, choice, canvas) {
|
|
5895
|
+
const b = boxOfPixels(entry.layer.bbox, canvas.width, canvas.height);
|
|
5896
|
+
if ("cellPx" in choice) {
|
|
5897
|
+
part.mesh = gridMesh(
|
|
5898
|
+
Math.max(1, Math.round(bw(b) / choice.cellPx)),
|
|
5899
|
+
Math.max(1, Math.round(bh(b) / choice.cellPx))
|
|
5900
|
+
);
|
|
5901
|
+
return;
|
|
5902
|
+
}
|
|
5903
|
+
const xs = Array.from(
|
|
5904
|
+
{ length: choice.cols + 1 },
|
|
5905
|
+
(_, c) => b.x0 + bw(b) * c / choice.cols
|
|
5906
|
+
);
|
|
5907
|
+
part.mesh = columnMesh(b, {
|
|
5908
|
+
xs,
|
|
5909
|
+
ys: choice.rows.map((y) => canvas.height / 2 - y)
|
|
5910
|
+
});
|
|
5911
|
+
}
|
|
5912
|
+
function applyBakes(parsed, parts, entries, ctx) {
|
|
5913
|
+
const { canvas, deformers: deformers2, parameters, faceWidth } = ctx;
|
|
5914
|
+
const partOf = (id) => parts.find((p) => p.id === id);
|
|
5915
|
+
const entryOf = (id) => entries.find((e) => e.id === id);
|
|
5916
|
+
for (const [id, choice] of parsed.meshes) {
|
|
5917
|
+
remesh(partOf(id), entryOf(id), choice, canvas);
|
|
5918
|
+
}
|
|
5919
|
+
const ids = [...new Set(parsed.targets.flatMap((t) => t.parts))];
|
|
5920
|
+
const reports = [];
|
|
5921
|
+
for (const id of ids) {
|
|
5922
|
+
const part = partOf(id);
|
|
5923
|
+
const n = part.mesh.vertices.length / 2;
|
|
5924
|
+
const own = parsed.targets.filter((t) => t.parts.includes(id));
|
|
5925
|
+
const w0 = matrixWorldAt(deformers2, parameters, part.deformer);
|
|
5926
|
+
const local = [];
|
|
5927
|
+
const restModel = [];
|
|
5928
|
+
const restCanvas = [];
|
|
5929
|
+
for (let i = 0; i < n; i++) {
|
|
5930
|
+
const p = [
|
|
5931
|
+
part.transform.x + part.mesh.vertices[i * 2] * part.width,
|
|
5932
|
+
part.transform.y + part.mesh.vertices[i * 2 + 1] * part.height
|
|
5933
|
+
];
|
|
5934
|
+
const r = apply(w0, p[0], p[1]);
|
|
5935
|
+
local.push(p);
|
|
5936
|
+
restModel.push(r);
|
|
5937
|
+
restCanvas.push([r[0] + canvas.width / 2, canvas.height / 2 - r[1]]);
|
|
5938
|
+
}
|
|
5939
|
+
for (const param of [...new Set(own.map((t) => t.param))]) {
|
|
5940
|
+
const targets = own.filter((t) => t.param === param);
|
|
5941
|
+
const rest = targets[0].rest;
|
|
5942
|
+
const stops = [];
|
|
5943
|
+
for (const v of targets.flatMap((t) => [...t.stops.keys()]).sort((a, b) => a - b)) {
|
|
5944
|
+
if (stops.length === 0 || v - stops[stops.length - 1] > SAME_STOP) {
|
|
5945
|
+
stops.push(v);
|
|
5946
|
+
}
|
|
5947
|
+
}
|
|
5948
|
+
const range = parameters.find((p) => p.id === param);
|
|
5949
|
+
const ends = [];
|
|
5950
|
+
for (const [outer, end] of [
|
|
5951
|
+
[stops.filter((v) => v < rest).shift(), range.min],
|
|
5952
|
+
[stops.filter((v) => v > rest).pop(), range.max]
|
|
5953
|
+
]) {
|
|
5954
|
+
if (outer !== void 0 && Math.abs(end - outer) > SAME_STOP) {
|
|
5955
|
+
ends.push(end);
|
|
5956
|
+
}
|
|
5957
|
+
}
|
|
5958
|
+
const keyforms = [{ value: rest, offsets: new Array(2 * n).fill(0) }];
|
|
5959
|
+
const wanted = [];
|
|
5960
|
+
const endWanted = [];
|
|
5961
|
+
for (const v of [...stops, ...ends]) {
|
|
5962
|
+
const disp = new Array(2 * n).fill(0);
|
|
5963
|
+
const fields2 = targets.map((t) => targetField(t, v));
|
|
5964
|
+
for (let i = 0; i < n; i++) {
|
|
5965
|
+
for (const field of fields2) {
|
|
5966
|
+
const [dx, dy] = field(restCanvas[i][0], restCanvas[i][1]);
|
|
5967
|
+
disp[i * 2] += dx;
|
|
5968
|
+
disp[i * 2 + 1] += dy;
|
|
5969
|
+
}
|
|
5970
|
+
}
|
|
5971
|
+
(ends.includes(v) ? endWanted : wanted).push(disp);
|
|
5972
|
+
if (entryOf(id).rigid === true) {
|
|
5973
|
+
const moved = restCanvas.map(
|
|
5974
|
+
(p, i) => [p[0] + disp[i * 2], p[1] + disp[i * 2 + 1]]
|
|
5975
|
+
);
|
|
5976
|
+
const bend = rigidResidual(restCanvas, moved);
|
|
5977
|
+
if (bend > RIGID_BEND * faceWidth) {
|
|
5978
|
+
const names = targets.map(
|
|
5979
|
+
(t) => `bakes.targets[${parsed.targets.indexOf(t)}] ("${t.id}")`
|
|
5980
|
+
).join(", ");
|
|
5981
|
+
throw new BakeError(
|
|
5982
|
+
`${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`
|
|
5983
|
+
);
|
|
5984
|
+
}
|
|
5985
|
+
}
|
|
5986
|
+
const inv = invert(
|
|
5987
|
+
matrixWorldAt(deformers2, parameters, part.deformer, { [param]: v })
|
|
5988
|
+
);
|
|
5989
|
+
const offsets = [];
|
|
5990
|
+
for (let i = 0; i < n; i++) {
|
|
5991
|
+
const [px, py] = apply(
|
|
5992
|
+
inv,
|
|
5993
|
+
restModel[i][0] + disp[i * 2],
|
|
5994
|
+
restModel[i][1] - disp[i * 2 + 1]
|
|
5995
|
+
);
|
|
5996
|
+
offsets.push(
|
|
5997
|
+
roundTo((px - local[i][0]) / part.width, OFFSET_STEP),
|
|
5998
|
+
roundTo((py - local[i][1]) / part.height, OFFSET_STEP)
|
|
5999
|
+
);
|
|
6000
|
+
}
|
|
6001
|
+
keyforms.push({ value: v, offsets });
|
|
6002
|
+
}
|
|
6003
|
+
keyforms.sort((a, b) => a.value - b.value);
|
|
6004
|
+
part.warps = [...part.warps ?? [], { parameter: param, keyforms }];
|
|
6005
|
+
reports.push({
|
|
6006
|
+
part: id,
|
|
6007
|
+
param,
|
|
6008
|
+
stops,
|
|
6009
|
+
vertexCount: n,
|
|
6010
|
+
wanted,
|
|
6011
|
+
ends: ends.map((value, i) => ({ value, wanted: endWanted[i] }))
|
|
6012
|
+
});
|
|
6013
|
+
}
|
|
6014
|
+
}
|
|
6015
|
+
return reports;
|
|
6016
|
+
}
|
|
6017
|
+
|
|
5328
6018
|
// src/auto-rig/generate.ts
|
|
5329
6019
|
import {
|
|
5330
6020
|
IKI_FORMAT_VERSION as IKI_FORMAT_VERSION2,
|
|
@@ -5533,8 +6223,8 @@ function defaultMotions(declared) {
|
|
|
5533
6223
|
}
|
|
5534
6224
|
|
|
5535
6225
|
// src/auto-rig/checks.ts
|
|
5536
|
-
var
|
|
5537
|
-
var
|
|
6226
|
+
var finite2 = (v) => typeof v === "number" && Number.isFinite(v);
|
|
6227
|
+
var isObject2 = (v) => typeof v === "object" && v !== null;
|
|
5538
6228
|
var MIN_FACE_PX = 24;
|
|
5539
6229
|
function checkLayers(layers, canvas) {
|
|
5540
6230
|
if (!Array.isArray(layers) || layers.length === 0) {
|
|
@@ -5542,10 +6232,10 @@ function checkLayers(layers, canvas) {
|
|
|
5542
6232
|
"auto-rig: generateIkiFromLayerSet: layers must be a non-empty array"
|
|
5543
6233
|
);
|
|
5544
6234
|
}
|
|
5545
|
-
if (!(
|
|
6235
|
+
if (!(finite2(canvas?.width) && canvas.width > 0)) {
|
|
5546
6236
|
throw new Error("auto-rig: canvas.width must be a positive number");
|
|
5547
6237
|
}
|
|
5548
|
-
if (!(
|
|
6238
|
+
if (!(finite2(canvas?.height) && canvas.height > 0)) {
|
|
5549
6239
|
throw new Error("auto-rig: canvas.height must be a positive number");
|
|
5550
6240
|
}
|
|
5551
6241
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -5557,7 +6247,7 @@ function checkLayers(layers, canvas) {
|
|
|
5557
6247
|
seen.add(l.role);
|
|
5558
6248
|
const at = `auto-rig: layer "${l.fileName}"`;
|
|
5559
6249
|
const { x, y, w, h } = l.bbox ?? {};
|
|
5560
|
-
if (![x, y, w, h].every(
|
|
6250
|
+
if (![x, y, w, h].every(finite2) || w <= 0 || h <= 0 || x < 0 || y < 0 || x + w > canvas.width || y + h > canvas.height) {
|
|
5561
6251
|
throw new Error(
|
|
5562
6252
|
`${at} has a bbox outside its ${canvas.width}x${canvas.height} canvas`
|
|
5563
6253
|
);
|
|
@@ -5585,7 +6275,7 @@ function checkLayers(layers, canvas) {
|
|
|
5585
6275
|
);
|
|
5586
6276
|
}
|
|
5587
6277
|
rows.forEach((v, i) => {
|
|
5588
|
-
if (!
|
|
6278
|
+
if (!finite2(v) || v < 0 || v > w / 2 + 0.5) {
|
|
5589
6279
|
throw new Error(
|
|
5590
6280
|
`${at}: rowHalfWidths[${i}] ${v} is not a half-width inside its crop`
|
|
5591
6281
|
);
|
|
@@ -5626,7 +6316,7 @@ function checkLayers(layers, canvas) {
|
|
|
5626
6316
|
}
|
|
5627
6317
|
if (l.denseCore !== void 0) {
|
|
5628
6318
|
const c = l.denseCore;
|
|
5629
|
-
if (![c.x, c.y, c.w, c.h].every(
|
|
6319
|
+
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) {
|
|
5630
6320
|
throw new Error(`${at}: denseCore is not a box inside its crop`);
|
|
5631
6321
|
}
|
|
5632
6322
|
}
|
|
@@ -5649,7 +6339,7 @@ function checkLayers(layers, canvas) {
|
|
|
5649
6339
|
}
|
|
5650
6340
|
function checkHeadEdges(edges, byRole) {
|
|
5651
6341
|
if (edges === void 0) return;
|
|
5652
|
-
if (!
|
|
6342
|
+
if (!isObject2(edges))
|
|
5653
6343
|
throw new TurnTargetError("headEdges must be an object");
|
|
5654
6344
|
for (const side of ["left", "right"]) {
|
|
5655
6345
|
const list = edges[side];
|
|
@@ -5657,12 +6347,12 @@ function checkHeadEdges(edges, byRole) {
|
|
|
5657
6347
|
throw new TurnTargetError(`headEdges.${side} must be an array`);
|
|
5658
6348
|
}
|
|
5659
6349
|
list.forEach((e, i) => {
|
|
5660
|
-
if (!
|
|
6350
|
+
if (!isObject2(e) || typeof e.role !== "string" || !byRole.has(e.role)) {
|
|
5661
6351
|
throw new TurnTargetError(
|
|
5662
6352
|
`headEdges.${side}[${i}].role is not a layer in this set`
|
|
5663
6353
|
);
|
|
5664
6354
|
}
|
|
5665
|
-
if (!
|
|
6355
|
+
if (!finite2(e.x)) {
|
|
5666
6356
|
throw new TurnTargetError(
|
|
5667
6357
|
`headEdges.${side}[${i}].x must be a finite number`
|
|
5668
6358
|
);
|
|
@@ -5682,25 +6372,25 @@ function checkHeadEdges(edges, byRole) {
|
|
|
5682
6372
|
}
|
|
5683
6373
|
function checkStrandEdges(strand, irises, hair) {
|
|
5684
6374
|
if (strand === void 0) return;
|
|
5685
|
-
if (!
|
|
6375
|
+
if (!isObject2(strand)) {
|
|
5686
6376
|
throw new TurnTargetError("strandEdges must be an object");
|
|
5687
6377
|
}
|
|
5688
6378
|
for (const side of ["left", "right"]) {
|
|
5689
6379
|
const s = strand[side];
|
|
5690
6380
|
if (s === void 0) continue;
|
|
5691
6381
|
const at = `strandEdges.${side}`;
|
|
5692
|
-
if (!
|
|
6382
|
+
if (!isObject2(s)) throw new TurnTargetError(`${at} must be an object`);
|
|
5693
6383
|
if (hair === void 0) {
|
|
5694
6384
|
throw new TurnTargetError(
|
|
5695
6385
|
`${at} names a bangs' run, but this layer set has no hair_front`
|
|
5696
6386
|
);
|
|
5697
6387
|
}
|
|
5698
6388
|
for (const k of ["y", "irisOuter", "irisInner", "runOuter"]) {
|
|
5699
|
-
if (!
|
|
6389
|
+
if (!finite2(s[k])) {
|
|
5700
6390
|
throw new TurnTargetError(`${at}.${k} must be a finite number`);
|
|
5701
6391
|
}
|
|
5702
6392
|
}
|
|
5703
|
-
if (s.runFace !== null && !
|
|
6393
|
+
if (s.runFace !== null && !finite2(s.runFace)) {
|
|
5704
6394
|
throw new TurnTargetError(
|
|
5705
6395
|
`${at}.runFace must be a finite number or null`
|
|
5706
6396
|
);
|
|
@@ -6171,9 +6861,9 @@ function replay(op, i, ctx, sums) {
|
|
|
6171
6861
|
const x = span2(`${name}.region.x`, region.x);
|
|
6172
6862
|
const y = span2(`${name}.region.y`, region.y);
|
|
6173
6863
|
const by = fields(`${name}.by`, o.by, BY_KEYS);
|
|
6174
|
-
const dx =
|
|
6175
|
-
const dy =
|
|
6176
|
-
const falloff = o.falloff === void 0 ? 0 :
|
|
6864
|
+
const dx = finite3(`${name}.by.dx`, by.dx);
|
|
6865
|
+
const dy = finite3(`${name}.by.dy`, by.dy);
|
|
6866
|
+
const falloff = o.falloff === void 0 ? 0 : finite3(`${name}.falloff`, o.falloff, 0);
|
|
6177
6867
|
if (o.why !== void 0) text(`${name}.why`, o.why);
|
|
6178
6868
|
const box = ctx.boxOf(role);
|
|
6179
6869
|
if (box === void 0) {
|
|
@@ -6341,10 +7031,10 @@ function fields(name, v, known) {
|
|
|
6341
7031
|
function numbers(name, v) {
|
|
6342
7032
|
const o = fields(name, v, Object.keys(v ?? {}));
|
|
6343
7033
|
return Object.fromEntries(
|
|
6344
|
-
Object.entries(o).map(([k, n]) => [k,
|
|
7034
|
+
Object.entries(o).map(([k, n]) => [k, finite3(`${name}.${k}`, n)])
|
|
6345
7035
|
);
|
|
6346
7036
|
}
|
|
6347
|
-
var
|
|
7037
|
+
var finite3 = (name, v, lo = -Infinity) => asPatch(() => checkedNumber(name, v, lo, Infinity));
|
|
6348
7038
|
function text(name, v) {
|
|
6349
7039
|
if (typeof v !== "string") {
|
|
6350
7040
|
throw new PatchError(`${name} must be a string, got ${String(v)}`);
|
|
@@ -6357,8 +7047,8 @@ function span2(name, v) {
|
|
|
6357
7047
|
`${name} must be [from, to], got ${JSON.stringify(v)}`
|
|
6358
7048
|
);
|
|
6359
7049
|
}
|
|
6360
|
-
const lo =
|
|
6361
|
-
const hi =
|
|
7050
|
+
const lo = finite3(`${name}[0]`, v[0]);
|
|
7051
|
+
const hi = finite3(`${name}[1]`, v[1]);
|
|
6362
7052
|
if (lo > hi) {
|
|
6363
7053
|
throw new PatchError(
|
|
6364
7054
|
`${name} [${lo}, ${hi}] runs backwards: ${name}[0] must not exceed ${name}[1]`
|
|
@@ -6367,6 +7057,112 @@ function span2(name, v) {
|
|
|
6367
7057
|
return [lo, hi];
|
|
6368
7058
|
}
|
|
6369
7059
|
|
|
7060
|
+
// src/auto-rig/custom-parts.ts
|
|
7061
|
+
var KEYS = ["id", "layer", "anchor", "tie", "after", "deformer", "rigid"];
|
|
7062
|
+
var LAYER_KEYS = ["fileName", "bbox"];
|
|
7063
|
+
var finite4 = (v) => typeof v === "number" && Number.isFinite(v);
|
|
7064
|
+
var isObject3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
7065
|
+
var inside = (b, [x, y]) => x >= b.x && x < b.x + b.w && y >= b.y && y < b.y + b.h;
|
|
7066
|
+
var RESERVED = new Set(
|
|
7067
|
+
ROLE_TABLE.flatMap((r) => [r.role, ...partIdsOfRole(r.role)])
|
|
7068
|
+
);
|
|
7069
|
+
function checkCustomParts(customParts, byRole, canvas) {
|
|
7070
|
+
if (!Array.isArray(customParts)) {
|
|
7071
|
+
throw new CustomPartError(
|
|
7072
|
+
`customParts must be an array, got ${customParts === null ? "null" : typeof customParts}`
|
|
7073
|
+
);
|
|
7074
|
+
}
|
|
7075
|
+
const present = [...byRole.keys()];
|
|
7076
|
+
const roleField = (at, v) => {
|
|
7077
|
+
const layer = typeof v === "string" ? byRole.get(v) : void 0;
|
|
7078
|
+
if (layer === void 0) {
|
|
7079
|
+
throw new CustomPartError(
|
|
7080
|
+
`${at} must be a role of the set, one of ${present.join(", ")}; got ${JSON.stringify(v)}`
|
|
7081
|
+
);
|
|
7082
|
+
}
|
|
7083
|
+
return layer;
|
|
7084
|
+
};
|
|
7085
|
+
const ids = /* @__PURE__ */ new Set();
|
|
7086
|
+
customParts.forEach((entry, i) => {
|
|
7087
|
+
const at = `customParts[${i}]`;
|
|
7088
|
+
if (!isObject3(entry)) {
|
|
7089
|
+
throw new CustomPartError(`${at} must be a plain object`);
|
|
7090
|
+
}
|
|
7091
|
+
checkedKeys(at, entry, KEYS, CustomPartError);
|
|
7092
|
+
const { id, layer, anchor, tie, after, deformer, rigid } = entry;
|
|
7093
|
+
if (typeof id !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(id)) {
|
|
7094
|
+
throw new CustomPartError(
|
|
7095
|
+
`${at}.id must be a plain identifier (letters, digits, underscore; not starting with a digit), got ${JSON.stringify(id)}`
|
|
7096
|
+
);
|
|
7097
|
+
}
|
|
7098
|
+
if (RESERVED.has(id)) {
|
|
7099
|
+
throw new CustomPartError(
|
|
7100
|
+
`${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`
|
|
7101
|
+
);
|
|
7102
|
+
}
|
|
7103
|
+
if (ids.has(id)) {
|
|
7104
|
+
throw new CustomPartError(
|
|
7105
|
+
`${at}.id "${id}" repeats an earlier entry's id`
|
|
7106
|
+
);
|
|
7107
|
+
}
|
|
7108
|
+
ids.add(id);
|
|
7109
|
+
if (!isObject3(layer)) {
|
|
7110
|
+
throw new CustomPartError(`${at}.layer must be a plain object`);
|
|
7111
|
+
}
|
|
7112
|
+
checkedKeys(`${at}.layer`, layer, LAYER_KEYS, CustomPartError);
|
|
7113
|
+
if (typeof layer.fileName !== "string" || layer.fileName === "") {
|
|
7114
|
+
throw new CustomPartError(
|
|
7115
|
+
`${at}.layer.fileName must be a non-empty string`
|
|
7116
|
+
);
|
|
7117
|
+
}
|
|
7118
|
+
const b = isObject3(layer.bbox) ? layer.bbox : {};
|
|
7119
|
+
const { x, y, w, h } = b;
|
|
7120
|
+
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) {
|
|
7121
|
+
throw new CustomPartError(
|
|
7122
|
+
`${at}.layer.bbox must be a finite, non-empty box inside the ${canvas.width}x${canvas.height} canvas, got ${JSON.stringify(layer.bbox)}`
|
|
7123
|
+
);
|
|
7124
|
+
}
|
|
7125
|
+
const box = { x, y, w, h };
|
|
7126
|
+
const anchorLayer = roleField(`${at}.anchor`, anchor);
|
|
7127
|
+
roleField(`${at}.after`, after);
|
|
7128
|
+
if (!Array.isArray(tie) || tie.length !== 2 || !tie.every(finite4) || !inside(box, tie) || !inside(anchorLayer.bbox, tie)) {
|
|
7129
|
+
throw new CustomPartError(
|
|
7130
|
+
`${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)}`
|
|
7131
|
+
);
|
|
7132
|
+
}
|
|
7133
|
+
if (deformer !== void 0 && deformer !== "headDeformer") {
|
|
7134
|
+
throw new CustomPartError(
|
|
7135
|
+
`${at}.deformer "${String(deformer)}" is not accepted: headDeformer is the only deformer a custom part hangs on`
|
|
7136
|
+
);
|
|
7137
|
+
}
|
|
7138
|
+
if (rigid !== void 0 && typeof rigid !== "boolean") {
|
|
7139
|
+
throw new CustomPartError(
|
|
7140
|
+
`${at}.rigid must be true or false, got ${JSON.stringify(rigid)}`
|
|
7141
|
+
);
|
|
7142
|
+
}
|
|
7143
|
+
});
|
|
7144
|
+
return customParts;
|
|
7145
|
+
}
|
|
7146
|
+
var CELL_PX2 = 20;
|
|
7147
|
+
function buildCustomParts(customParts, canvas) {
|
|
7148
|
+
return customParts.map(({ id, layer }) => {
|
|
7149
|
+
const b = boxOfPixels(layer.bbox, canvas.width, canvas.height);
|
|
7150
|
+
return {
|
|
7151
|
+
id,
|
|
7152
|
+
color: [1, 1, 1, 1],
|
|
7153
|
+
width: bw(b),
|
|
7154
|
+
height: bh(b),
|
|
7155
|
+
transform: { x: cx(b), y: cy(b) },
|
|
7156
|
+
order: 0,
|
|
7157
|
+
deformer: "headDeformer",
|
|
7158
|
+
mesh: gridMesh(
|
|
7159
|
+
Math.max(1, Math.round(bw(b) / CELL_PX2)),
|
|
7160
|
+
Math.max(1, Math.round(bh(b) / CELL_PX2))
|
|
7161
|
+
)
|
|
7162
|
+
};
|
|
7163
|
+
});
|
|
7164
|
+
}
|
|
7165
|
+
|
|
6370
7166
|
// src/auto-rig/generate.ts
|
|
6371
7167
|
var MESH_CELLS = {
|
|
6372
7168
|
face: { px: 28, min: 4, max: 24 },
|
|
@@ -6408,6 +7204,9 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
6408
7204
|
const hasNose = has("nose");
|
|
6409
7205
|
const plateHalf = byRole.get("face").cropW / 2;
|
|
6410
7206
|
checkHeadEdges(options.headEdges, byRole);
|
|
7207
|
+
const entries = options.customParts !== void 0 && !(Array.isArray(options.customParts) && options.customParts.length === 0) ? checkCustomParts(options.customParts, byRole, canvas) : [];
|
|
7208
|
+
const parameters = declareParameters(new Set(byRole.keys()));
|
|
7209
|
+
const bakes = options.bakes === void 0 ? void 0 : parseBakes(options.bakes, entries, parameters);
|
|
6411
7210
|
checkStrandEdges(
|
|
6412
7211
|
options.strandEdges,
|
|
6413
7212
|
sideIrises(byRole),
|
|
@@ -6557,7 +7356,6 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
6557
7356
|
...part.warps ?? []
|
|
6558
7357
|
];
|
|
6559
7358
|
}
|
|
6560
|
-
const parameters = declareParameters(new Set(byRole.keys()));
|
|
6561
7359
|
const reports = options.patches === void 0 ? void 0 : applyPatches(options.patches, {
|
|
6562
7360
|
grids: new Map(
|
|
6563
7361
|
[...grids].map(([family, g]) => [WARP_ID[family], g])
|
|
@@ -6567,19 +7365,50 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
6567
7365
|
faceWidth: byRole.get("face").cropW,
|
|
6568
7366
|
parameters
|
|
6569
7367
|
});
|
|
7368
|
+
const deformerList = deformers(
|
|
7369
|
+
frame,
|
|
7370
|
+
grids,
|
|
7371
|
+
bodyWarp?.deformer,
|
|
7372
|
+
arms,
|
|
7373
|
+
byRole
|
|
7374
|
+
);
|
|
7375
|
+
let bakeReports;
|
|
7376
|
+
if (entries.length > 0) {
|
|
7377
|
+
const built = buildCustomParts(entries, canvas);
|
|
7378
|
+
entries.forEach((entry, i) => {
|
|
7379
|
+
const group = new Set(partIdsOfRole(entry.after));
|
|
7380
|
+
for (let j = 0; j < i; j++) {
|
|
7381
|
+
if (entries[j].after === entry.after) group.add(entries[j].id);
|
|
7382
|
+
}
|
|
7383
|
+
const last = parts.map((p) => group.has(p.id)).lastIndexOf(true);
|
|
7384
|
+
parts.splice(last + 1, 0, built[i]);
|
|
7385
|
+
});
|
|
7386
|
+
parts.forEach((p, i) => {
|
|
7387
|
+
p.order = i;
|
|
7388
|
+
});
|
|
7389
|
+
if (bakes !== void 0) {
|
|
7390
|
+
bakeReports = applyBakes(bakes, built, entries, {
|
|
7391
|
+
canvas,
|
|
7392
|
+
deformers: deformerList,
|
|
7393
|
+
parameters,
|
|
7394
|
+
faceWidth: byRole.get("face").cropW
|
|
7395
|
+
});
|
|
7396
|
+
}
|
|
7397
|
+
}
|
|
6570
7398
|
const model = {
|
|
6571
7399
|
version: IKI_FORMAT_VERSION2,
|
|
6572
7400
|
name: "auto-rigged",
|
|
6573
7401
|
canvas: { width: canvas.width, height: canvas.height },
|
|
6574
7402
|
parameters,
|
|
6575
7403
|
parts,
|
|
6576
|
-
deformers:
|
|
7404
|
+
deformers: deformerList,
|
|
6577
7405
|
...has("hair_front") ? { physics: hairPhysics(has("hair_back")) } : {},
|
|
6578
7406
|
expressions: defaultExpressions(new Set(parameters.map((p) => p.id))),
|
|
6579
7407
|
motions: defaultMotions(new Set(parameters.map((p) => p.id)))
|
|
6580
7408
|
};
|
|
6581
7409
|
const valid = parseIkiModel3(model);
|
|
6582
7410
|
if (reports !== void 0) options.onPatchesApplied?.(reports);
|
|
7411
|
+
if (bakeReports !== void 0) options.onBakesApplied?.(bakeReports);
|
|
6583
7412
|
options.onRigPlanResolved?.(
|
|
6584
7413
|
rigPlanReport(options.rigPlan ?? {}, plan, targets, fit)
|
|
6585
7414
|
);
|
|
@@ -7061,7 +7890,9 @@ export {
|
|
|
7061
7890
|
AddDeformer,
|
|
7062
7891
|
AddPart,
|
|
7063
7892
|
AddPhysicsRig,
|
|
7893
|
+
BakeError,
|
|
7064
7894
|
CaptureGridKeyform,
|
|
7895
|
+
CustomPartError,
|
|
7065
7896
|
DEFAULT_TURN_TARGETS,
|
|
7066
7897
|
DeleteDeformer,
|
|
7067
7898
|
DeletePart,
|
|
@@ -7117,6 +7948,7 @@ export {
|
|
|
7117
7948
|
packAtlas,
|
|
7118
7949
|
parseLayerRoles,
|
|
7119
7950
|
partIdsOfRole,
|
|
7951
|
+
rigidResidual,
|
|
7120
7952
|
styleRemovedMessage,
|
|
7121
7953
|
upsertGridKeyform,
|
|
7122
7954
|
uvRectFor,
|