@ikijs/editor 0.19.0 → 0.21.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 +18 -14
- package/dist/index.d.mts +74 -17
- package/dist/index.d.ts +74 -17
- package/dist/index.js +574 -157
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +575 -155
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1565,7 +1565,16 @@ var ROW_RUN_ROLES = /* @__PURE__ */ new Set([
|
|
|
1565
1565
|
"hair_back",
|
|
1566
1566
|
"body",
|
|
1567
1567
|
"arm_L",
|
|
1568
|
-
"arm_R"
|
|
1568
|
+
"arm_R",
|
|
1569
|
+
"forearm_pose_L",
|
|
1570
|
+
"forearm_pose_R",
|
|
1571
|
+
"mouth_inner",
|
|
1572
|
+
"lip_lower",
|
|
1573
|
+
"lip_upper"
|
|
1574
|
+
]);
|
|
1575
|
+
var REST_HIDDEN_ROLES = /* @__PURE__ */ new Set([
|
|
1576
|
+
"forearm_pose_L",
|
|
1577
|
+
"forearm_pose_R"
|
|
1569
1578
|
]);
|
|
1570
1579
|
function createLayerSetMeasurer(canvas) {
|
|
1571
1580
|
for (const field of ["width", "height"]) {
|
|
@@ -1596,6 +1605,7 @@ function createLayerSetMeasurer(canvas) {
|
|
|
1596
1605
|
if (bbox === null) return null;
|
|
1597
1606
|
const rowLeft = new Int32Array(canvasH).fill(canvasW);
|
|
1598
1607
|
const rowRight = new Int32Array(canvasH).fill(-1);
|
|
1608
|
+
const hidden = REST_HIDDEN_ROLES.has(role);
|
|
1599
1609
|
const rowRuns = ROW_RUN_ROLES.has(role) ? [] : void 0;
|
|
1600
1610
|
for (let y = 0; y < canvasH; y++) {
|
|
1601
1611
|
let runs;
|
|
@@ -1611,14 +1621,14 @@ function createLayerSetMeasurer(canvas) {
|
|
|
1611
1621
|
runs.push(x);
|
|
1612
1622
|
inRun = on;
|
|
1613
1623
|
}
|
|
1614
|
-
if (!on) continue;
|
|
1624
|
+
if (!on || hidden) continue;
|
|
1615
1625
|
opaque[p] = 1;
|
|
1616
1626
|
if (x < rowLeft[y]) rowLeft[y] = x;
|
|
1617
1627
|
if (x > rowRight[y]) rowRight[y] = x;
|
|
1618
1628
|
}
|
|
1619
1629
|
if (runs !== void 0 && inRun) runs.push(canvasW);
|
|
1620
1630
|
}
|
|
1621
|
-
rowSpansByRole.push({ role, rowLeft, rowRight });
|
|
1631
|
+
if (!hidden) rowSpansByRole.push({ role, rowLeft, rowRight });
|
|
1622
1632
|
const layer = {
|
|
1623
1633
|
role,
|
|
1624
1634
|
fileName,
|
|
@@ -2177,6 +2187,29 @@ function gridMesh(cols, rows) {
|
|
|
2177
2187
|
}
|
|
2178
2188
|
return { vertices, uvs, indices };
|
|
2179
2189
|
}
|
|
2190
|
+
function columnMesh(b, grid) {
|
|
2191
|
+
const { xs } = grid;
|
|
2192
|
+
const rows = grid.ys.length - 1;
|
|
2193
|
+
const vertices = [];
|
|
2194
|
+
const uvs = [];
|
|
2195
|
+
for (let r = 0; r <= rows; r++) {
|
|
2196
|
+
for (const x of xs) {
|
|
2197
|
+
const u = (x - b.x0) / bw(b);
|
|
2198
|
+
vertices.push(roundTo(u - 0.5, 1e-5), roundTo(0.5 - r / rows, 1e-5));
|
|
2199
|
+
uvs.push(roundTo(u, 1e-5), roundTo(r / rows, 1e-5));
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
const n = xs.length;
|
|
2203
|
+
const indices = [];
|
|
2204
|
+
for (let r = 0; r < rows; r++) {
|
|
2205
|
+
for (let c = 0; c + 1 < n; c++) {
|
|
2206
|
+
const tl = r * n + c;
|
|
2207
|
+
const bl = tl + n;
|
|
2208
|
+
indices.push(bl, bl + 1, tl, tl, bl + 1, tl + 1);
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
return { vertices, uvs, indices };
|
|
2212
|
+
}
|
|
2180
2213
|
function meshPoints(mesh, box) {
|
|
2181
2214
|
const out = [];
|
|
2182
2215
|
for (let i = 0; i < mesh.vertices.length; i += 2) {
|
|
@@ -2302,10 +2335,13 @@ function browBindings(side, reach) {
|
|
|
2302
2335
|
];
|
|
2303
2336
|
}
|
|
2304
2337
|
var BROW_TILT = 15;
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2338
|
+
var mouthFrameOf = (box) => ({
|
|
2339
|
+
c: cx(box),
|
|
2340
|
+
half: bw(box) / 2,
|
|
2341
|
+
amp: 0.3 * bh(box)
|
|
2342
|
+
});
|
|
2343
|
+
function mouthForm(mesh, box, frame = mouthFrameOf(box)) {
|
|
2344
|
+
const { c, half, amp: a } = frame;
|
|
2309
2345
|
return localWarp(P.MouthForm, mesh, box, [-1, 0, 1], ([x], v) => {
|
|
2310
2346
|
if (v === 0) return [0, 0];
|
|
2311
2347
|
const u = (x - c) / half;
|
|
@@ -2323,8 +2359,8 @@ function mouthOpenGrow(mesh, box) {
|
|
|
2323
2359
|
([x, y], v) => v === 1 ? [(AMPLITUDE.mouthOpenWidth - 1) * (x - c), 0] : [0, box.y1 + (y - box.y1) * 0.2 - y]
|
|
2324
2360
|
);
|
|
2325
2361
|
}
|
|
2326
|
-
function mouthWiden(mesh, box) {
|
|
2327
|
-
const c =
|
|
2362
|
+
function mouthWiden(mesh, box, frame = mouthFrameOf(box)) {
|
|
2363
|
+
const c = frame.c;
|
|
2328
2364
|
return localWarp(P.MouthOpen, mesh, box, [0, 1], ([x], v) => [
|
|
2329
2365
|
v * (AMPLITUDE.mouthOpenWidth - 1) * (x - c),
|
|
2330
2366
|
0
|
|
@@ -2707,40 +2743,56 @@ var ARM_IDS = {
|
|
|
2707
2743
|
forearmDeformer: "forearmDeformer_R"
|
|
2708
2744
|
}
|
|
2709
2745
|
};
|
|
2710
|
-
function
|
|
2746
|
+
function endPivot(layer, end) {
|
|
2711
2747
|
const { bbox, canvasW, canvasH, rowRuns } = layer;
|
|
2712
|
-
const
|
|
2713
|
-
const
|
|
2748
|
+
const span = runSpan(layer);
|
|
2749
|
+
const from = (k) => end === "top" ? k : bbox.h - 1 - k;
|
|
2750
|
+
const painted = rowRuns?.flatMap((runs, k) => runs.length > 0 ? [k] : []) ?? [];
|
|
2751
|
+
const firstK = end === "top" ? painted[0] ?? 0 : painted.at(-1) ?? bbox.h - 1;
|
|
2752
|
+
const lo = Math.ceil(CAP_ROWS[0] * bbox.h);
|
|
2753
|
+
const hi = Math.max(lo, Math.floor(CAP_ROWS[1] * bbox.h));
|
|
2754
|
+
const widths = [];
|
|
2755
|
+
for (let k = lo; k <= hi; k++) {
|
|
2756
|
+
const [s2, e2] = span(from(k));
|
|
2757
|
+
widths.push(e2 - s2);
|
|
2758
|
+
}
|
|
2759
|
+
widths.sort((p, q) => p - q);
|
|
2760
|
+
const radius = widths[widths.length >> 1] / 2;
|
|
2761
|
+
const ks = end === "top" ? firstK + radius : firstK - radius;
|
|
2762
|
+
const [s, e] = span(Math.round(ks));
|
|
2763
|
+
return {
|
|
2764
|
+
point: {
|
|
2765
|
+
x: roundTo((s + e) / 2 - canvasW / 2, 0.01),
|
|
2766
|
+
y: roundTo(canvasH / 2 - (bbox.y + ks + 0.5), 0.01)
|
|
2767
|
+
},
|
|
2768
|
+
radius,
|
|
2769
|
+
row: ks
|
|
2770
|
+
};
|
|
2771
|
+
}
|
|
2772
|
+
function runSpan(layer) {
|
|
2773
|
+
const { bbox, rowRuns } = layer;
|
|
2774
|
+
return (k) => {
|
|
2714
2775
|
const runs = rowRuns?.[k] ?? [];
|
|
2715
2776
|
if (runs.length === 0) return [bbox.x, bbox.x + bbox.w];
|
|
2716
|
-
let [
|
|
2777
|
+
let [s, e] = [runs[0], runs[1]];
|
|
2717
2778
|
for (let i = 2; i < runs.length; i += 2) {
|
|
2718
|
-
if (runs[i + 1] - runs[i] >
|
|
2779
|
+
if (runs[i + 1] - runs[i] > e - s) [s, e] = [runs[i], runs[i + 1]];
|
|
2719
2780
|
}
|
|
2720
|
-
return [
|
|
2781
|
+
return [s, e];
|
|
2721
2782
|
};
|
|
2783
|
+
}
|
|
2784
|
+
function armGeometry(layer, bodyAxisX) {
|
|
2785
|
+
const { bbox, canvasW, canvasH, rowRuns } = layer;
|
|
2786
|
+
const b = boxOfLayer(layer);
|
|
2787
|
+
const span = runSpan(layer);
|
|
2722
2788
|
const centreX = (k) => {
|
|
2723
2789
|
const [s2, e2] = span(k);
|
|
2724
2790
|
return (s2 + e2) / 2 - canvasW / 2;
|
|
2725
2791
|
};
|
|
2726
2792
|
const rowY = (k) => canvasH / 2 - (bbox.y + k + 0.5);
|
|
2727
2793
|
const painted = rowRuns?.flatMap((runs, k) => runs.length > 0 ? [k] : []) ?? [];
|
|
2728
|
-
const first = painted[0] ?? 0;
|
|
2729
2794
|
const last = painted.at(-1) ?? bbox.h - 1;
|
|
2730
|
-
const
|
|
2731
|
-
const hi = Math.max(lo, Math.floor(CAP_ROWS[1] * bbox.h));
|
|
2732
|
-
const widths = [];
|
|
2733
|
-
for (let k = lo; k <= hi; k++) {
|
|
2734
|
-
const [s2, e2] = span(k);
|
|
2735
|
-
widths.push(e2 - s2);
|
|
2736
|
-
}
|
|
2737
|
-
widths.sort((p, q) => p - q);
|
|
2738
|
-
const ru = widths[widths.length >> 1] / 2;
|
|
2739
|
-
const ks = first + ru;
|
|
2740
|
-
const shoulder = {
|
|
2741
|
-
x: roundTo(centreX(Math.round(ks)), 0.01),
|
|
2742
|
-
y: roundTo(rowY(ks), 0.01)
|
|
2743
|
-
};
|
|
2795
|
+
const { point: shoulder, radius: ru, row: ks } = endPivot(layer, "top");
|
|
2744
2796
|
const ke = Math.round(ks + ELBOW_AT * (last - ks));
|
|
2745
2797
|
if (ke <= ks) {
|
|
2746
2798
|
throw new LayerGeometryError(
|
|
@@ -2788,7 +2840,7 @@ function meshOf(vertices, indices) {
|
|
|
2788
2840
|
);
|
|
2789
2841
|
return { vertices, uvs, indices };
|
|
2790
2842
|
}
|
|
2791
|
-
function armParts(role, layer, g, order) {
|
|
2843
|
+
function armParts(role, layer, g, order, poseSwitch) {
|
|
2792
2844
|
const b = boxOfLayer(layer);
|
|
2793
2845
|
const lx = (x) => roundTo((x - cx(b)) / bw(b), 1e-5);
|
|
2794
2846
|
const ly = (y) => roundTo((y - cy(b)) / bh(b), 1e-5);
|
|
@@ -2841,7 +2893,7 @@ function armParts(role, layer, g, order) {
|
|
|
2841
2893
|
const [p, q] = [ringInner + j, ringOuter + j];
|
|
2842
2894
|
ring.push(p, q, q + 1, p, q + 1, p + 1);
|
|
2843
2895
|
}
|
|
2844
|
-
const part = (id, deformer, z, mesh) => ({
|
|
2896
|
+
const part = (id, deformer, z, mesh, swaps = false) => ({
|
|
2845
2897
|
id,
|
|
2846
2898
|
color: [1, 1, 1, 1],
|
|
2847
2899
|
width: bw(b),
|
|
@@ -2849,50 +2901,126 @@ function armParts(role, layer, g, order) {
|
|
|
2849
2901
|
transform: { x: cx(b), y: cy(b) },
|
|
2850
2902
|
order: z,
|
|
2851
2903
|
deformer,
|
|
2852
|
-
mesh
|
|
2904
|
+
mesh,
|
|
2905
|
+
...swaps && poseSwitch !== void 0 ? {
|
|
2906
|
+
bindings: [
|
|
2907
|
+
{
|
|
2908
|
+
parameter: poseSwitch,
|
|
2909
|
+
channel: "opacity",
|
|
2910
|
+
from: 1,
|
|
2911
|
+
to: 0
|
|
2912
|
+
}
|
|
2913
|
+
]
|
|
2914
|
+
} : {}
|
|
2853
2915
|
});
|
|
2854
2916
|
const ids = ARM_IDS[role];
|
|
2855
2917
|
return [
|
|
2856
|
-
part(
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2918
|
+
part(
|
|
2919
|
+
ids.cap,
|
|
2920
|
+
ids.forearmDeformer,
|
|
2921
|
+
order,
|
|
2922
|
+
{
|
|
2923
|
+
vertices,
|
|
2924
|
+
uvs,
|
|
2925
|
+
indices: [...fan, ...ring]
|
|
2926
|
+
},
|
|
2927
|
+
true
|
|
2928
|
+
),
|
|
2861
2929
|
part(role, ids.armDeformer, order + 1, meshOf(band(0.5, seam), quad)),
|
|
2862
2930
|
part(
|
|
2863
2931
|
ids.forearm,
|
|
2864
2932
|
ids.forearmDeformer,
|
|
2865
2933
|
order + 2,
|
|
2866
|
-
meshOf(band(seam, -0.5), quad)
|
|
2934
|
+
meshOf(band(seam, -0.5), quad),
|
|
2935
|
+
true
|
|
2867
2936
|
)
|
|
2868
2937
|
];
|
|
2869
2938
|
}
|
|
2939
|
+
var rotateBinding = (parameter, range, sign) => ({
|
|
2940
|
+
parameter,
|
|
2941
|
+
channel: "rotate",
|
|
2942
|
+
from: sign * range[0],
|
|
2943
|
+
to: sign * range[1]
|
|
2944
|
+
});
|
|
2870
2945
|
function armDeformers(role, g) {
|
|
2871
2946
|
const ids = ARM_IDS[role];
|
|
2872
2947
|
const params = ARM_PARAMS[role];
|
|
2873
|
-
const rotate = (parameter, range, sign) => ({
|
|
2874
|
-
parameter,
|
|
2875
|
-
channel: "rotate",
|
|
2876
|
-
from: sign * range[0],
|
|
2877
|
-
to: sign * range[1]
|
|
2878
|
-
});
|
|
2879
2948
|
return [
|
|
2880
2949
|
{
|
|
2881
2950
|
id: ids.armDeformer,
|
|
2882
2951
|
parent: BODY_WARP_ID,
|
|
2883
2952
|
pivot: { ...g.shoulder },
|
|
2884
|
-
bindings: [
|
|
2953
|
+
bindings: [rotateBinding(params.arm, ARM_RANGE, g.side)]
|
|
2885
2954
|
},
|
|
2886
2955
|
{
|
|
2887
2956
|
id: ids.forearmDeformer,
|
|
2888
2957
|
parent: ids.armDeformer,
|
|
2889
2958
|
pivot: { ...g.elbow },
|
|
2890
|
-
bindings: [
|
|
2959
|
+
bindings: [rotateBinding(params.elbow, ELBOW_RANGE, -g.side)]
|
|
2891
2960
|
}
|
|
2892
2961
|
];
|
|
2893
2962
|
}
|
|
2894
2963
|
|
|
2964
|
+
// src/auto-rig/forearm-pose.ts
|
|
2965
|
+
import {
|
|
2966
|
+
StandardParameter as P4
|
|
2967
|
+
} from "@ikijs/format";
|
|
2968
|
+
var ARM_OF_POSE = {
|
|
2969
|
+
forearm_pose_L: "arm_L",
|
|
2970
|
+
forearm_pose_R: "arm_R"
|
|
2971
|
+
};
|
|
2972
|
+
var POSE_OF_ARM = {
|
|
2973
|
+
arm_L: "forearm_pose_L",
|
|
2974
|
+
arm_R: "forearm_pose_R"
|
|
2975
|
+
};
|
|
2976
|
+
var POSE_PARAMS = {
|
|
2977
|
+
forearm_pose_L: { pose: P4.ArmPoseLeft, angle: P4.ArmPoseAngleLeft },
|
|
2978
|
+
forearm_pose_R: { pose: P4.ArmPoseRight, angle: P4.ArmPoseAngleRight }
|
|
2979
|
+
};
|
|
2980
|
+
var POSE_IDS = {
|
|
2981
|
+
forearm_pose_L: "armPoseDeformer_L",
|
|
2982
|
+
forearm_pose_R: "armPoseDeformer_R"
|
|
2983
|
+
};
|
|
2984
|
+
var POSE_ANGLE_RANGE = [-15, 15];
|
|
2985
|
+
function forearmPoseGeometry(layer) {
|
|
2986
|
+
const { point, radius } = endPivot(layer, "bottom");
|
|
2987
|
+
const first = layer.rowRuns?.findIndex((runs) => runs.length > 0) ?? -1;
|
|
2988
|
+
const k = first < 0 ? 0 : first;
|
|
2989
|
+
return {
|
|
2990
|
+
pivot: point,
|
|
2991
|
+
radius,
|
|
2992
|
+
top: roundTo(layer.canvasH / 2 - (layer.bbox.y + k + 0.5), 0.01)
|
|
2993
|
+
};
|
|
2994
|
+
}
|
|
2995
|
+
function forearmPosePart(role, layer, order) {
|
|
2996
|
+
const b = boxOfLayer(layer);
|
|
2997
|
+
return {
|
|
2998
|
+
id: role,
|
|
2999
|
+
color: [1, 1, 1, 1],
|
|
3000
|
+
width: bw(b),
|
|
3001
|
+
height: bh(b),
|
|
3002
|
+
transform: { x: cx(b), y: cy(b) },
|
|
3003
|
+
order,
|
|
3004
|
+
deformer: POSE_IDS[role],
|
|
3005
|
+
bindings: [
|
|
3006
|
+
{ parameter: POSE_PARAMS[role].pose, channel: "opacity", from: 0, to: 1 }
|
|
3007
|
+
]
|
|
3008
|
+
};
|
|
3009
|
+
}
|
|
3010
|
+
function forearmPoseDeformer(role, arm) {
|
|
3011
|
+
return {
|
|
3012
|
+
id: POSE_IDS[role],
|
|
3013
|
+
parent: ARM_IDS[ARM_OF_POSE[role]].armDeformer,
|
|
3014
|
+
pivot: { ...arm.elbow },
|
|
3015
|
+
bindings: [
|
|
3016
|
+
rotateBinding(POSE_PARAMS[role].angle, POSE_ANGLE_RANGE, -arm.side)
|
|
3017
|
+
]
|
|
3018
|
+
};
|
|
3019
|
+
}
|
|
3020
|
+
|
|
2895
3021
|
// src/auto-rig/roles.ts
|
|
3022
|
+
var LIP_ROLES = ["mouth_inner", "lip_lower", "lip_upper"];
|
|
3023
|
+
var isLipRole = (role) => LIP_ROLES.includes(role);
|
|
2896
3024
|
var ROLE_TABLE = [
|
|
2897
3025
|
{ role: "hair_back", family: "hair_back" },
|
|
2898
3026
|
{ role: "body", family: "body" },
|
|
@@ -2902,8 +3030,14 @@ var ROLE_TABLE = [
|
|
|
2902
3030
|
{ role: "blush_L", family: "face" },
|
|
2903
3031
|
{ role: "blush_R", family: "face" },
|
|
2904
3032
|
{ role: "nose", family: "nose" },
|
|
2905
|
-
{ role: "mouth", family: "mouth"
|
|
3033
|
+
{ role: "mouth", family: "mouth" },
|
|
2906
3034
|
{ role: "mouth_open", family: "mouth" },
|
|
3035
|
+
// The lip set, back to front: the interior, the lower lip's skin, the upper
|
|
3036
|
+
// lip and its line over both. It folds open instead of crossfading
|
|
3037
|
+
// (`mouth.ts`), so it stands in for `mouth`, never beside it.
|
|
3038
|
+
{ role: "mouth_inner", family: "mouth" },
|
|
3039
|
+
{ role: "lip_lower", family: "mouth" },
|
|
3040
|
+
{ role: "lip_upper", family: "mouth" },
|
|
2907
3041
|
{ role: "eye_L", family: "eye_L", required: true },
|
|
2908
3042
|
{ role: "eye_R", family: "eye_R", required: true },
|
|
2909
3043
|
{ role: "iris_L", family: "eye_L" },
|
|
@@ -2918,7 +3052,10 @@ var ROLE_TABLE = [
|
|
|
2918
3052
|
{ role: "lash_R", family: "eye_R" },
|
|
2919
3053
|
{ role: "brow_L", family: "brow_L" },
|
|
2920
3054
|
{ role: "brow_R", family: "brow_R" },
|
|
2921
|
-
{ role: "hair_front", family: "hair_front" }
|
|
3055
|
+
{ role: "hair_front", family: "hair_front" },
|
|
3056
|
+
// Drawn above the face and the hair: a raised hand passes them.
|
|
3057
|
+
{ role: "forearm_pose_L", family: "forearm_pose" },
|
|
3058
|
+
{ role: "forearm_pose_R", family: "forearm_pose" }
|
|
2922
3059
|
];
|
|
2923
3060
|
var REQUIRED_ROLES = ROLE_TABLE.filter(
|
|
2924
3061
|
(r) => r.required
|
|
@@ -2958,6 +3095,39 @@ function checkArmsHaveBody(roles) {
|
|
|
2958
3095
|
);
|
|
2959
3096
|
}
|
|
2960
3097
|
}
|
|
3098
|
+
function checkMouth(roles) {
|
|
3099
|
+
const has = new Set(roles);
|
|
3100
|
+
const present = LIP_ROLES.filter((r) => has.has(r));
|
|
3101
|
+
if (present.length === 0) {
|
|
3102
|
+
if (!has.has("mouth")) {
|
|
3103
|
+
throw new Error(
|
|
3104
|
+
`auto-rig: missing required role mouth (or the lip set ${LIP_ROLES.join(", ")})`
|
|
3105
|
+
);
|
|
3106
|
+
}
|
|
3107
|
+
return;
|
|
3108
|
+
}
|
|
3109
|
+
if (present.length < LIP_ROLES.length) {
|
|
3110
|
+
throw new Error(
|
|
3111
|
+
`auto-rig: the lip set is partial: missing ${LIP_ROLES.filter((r) => !has.has(r)).join(", ")}`
|
|
3112
|
+
);
|
|
3113
|
+
}
|
|
3114
|
+
const mixed = ["mouth", "mouth_open"].filter((r) => has.has(r));
|
|
3115
|
+
if (mixed.length > 0) {
|
|
3116
|
+
throw new Error(
|
|
3117
|
+
`auto-rig: the lip set cannot be mixed with ${mixed.join(", ")} (the lip set replaces the mouth drawings; use one or the other)`
|
|
3118
|
+
);
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
function checkPosesHaveArms(roles) {
|
|
3122
|
+
const has = new Set(roles);
|
|
3123
|
+
for (const side of ["L", "R"]) {
|
|
3124
|
+
if (has.has(`forearm_pose_${side}`) && !has.has(`arm_${side}`)) {
|
|
3125
|
+
throw new Error(
|
|
3126
|
+
`auto-rig: forearm_pose_${side} needs an arm_${side} layer (a pose forearm hangs from its arm's elbow)`
|
|
3127
|
+
);
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
}
|
|
2961
3131
|
function parseLayerRoles(fileNames) {
|
|
2962
3132
|
const seen = /* @__PURE__ */ new Map();
|
|
2963
3133
|
const out = [];
|
|
@@ -2983,43 +3153,45 @@ function parseLayerRoles(fileNames) {
|
|
|
2983
3153
|
`auto-rig: missing required role${missing.length > 1 ? "s" : ""} ${missing.join(", ")}`
|
|
2984
3154
|
);
|
|
2985
3155
|
}
|
|
3156
|
+
checkMouth(seen.keys());
|
|
2986
3157
|
checkArmsHaveBody(seen.keys());
|
|
3158
|
+
checkPosesHaveArms(seen.keys());
|
|
2987
3159
|
return out;
|
|
2988
3160
|
}
|
|
2989
3161
|
|
|
2990
3162
|
// src/auto-rig/generate.ts
|
|
2991
3163
|
import {
|
|
2992
3164
|
IKI_FORMAT_VERSION as IKI_FORMAT_VERSION2,
|
|
2993
|
-
StandardParameter as
|
|
3165
|
+
StandardParameter as P8,
|
|
2994
3166
|
parseIkiModel as parseIkiModel3
|
|
2995
3167
|
} from "@ikijs/format";
|
|
2996
3168
|
|
|
2997
3169
|
// src/auto-rig/animations.ts
|
|
2998
3170
|
import {
|
|
2999
|
-
StandardParameter as
|
|
3171
|
+
StandardParameter as P5
|
|
3000
3172
|
} from "@ikijs/format";
|
|
3001
3173
|
function browsY(y) {
|
|
3002
3174
|
return [
|
|
3003
|
-
{ parameter:
|
|
3004
|
-
{ parameter:
|
|
3175
|
+
{ parameter: P5.BrowLeftY, value: y, blend: "add" },
|
|
3176
|
+
{ parameter: P5.BrowRightY, value: y, blend: "add" }
|
|
3005
3177
|
];
|
|
3006
3178
|
}
|
|
3007
3179
|
function browsTilt(innerDown) {
|
|
3008
3180
|
return [
|
|
3009
|
-
{ parameter:
|
|
3010
|
-
{ parameter:
|
|
3181
|
+
{ parameter: P5.BrowLeftAngle, value: innerDown, blend: "add" },
|
|
3182
|
+
{ parameter: P5.BrowRightAngle, value: -innerDown, blend: "add" }
|
|
3011
3183
|
];
|
|
3012
3184
|
}
|
|
3013
3185
|
function cheek(value) {
|
|
3014
|
-
return { parameter:
|
|
3186
|
+
return { parameter: P5.Cheek, value, blend: "add" };
|
|
3015
3187
|
}
|
|
3016
|
-
var BROWS = [
|
|
3188
|
+
var BROWS = [P5.BrowLeftY, P5.BrowRightY, P5.BrowLeftAngle, P5.BrowRightAngle];
|
|
3017
3189
|
var EXPRESSIONS = [
|
|
3018
3190
|
{
|
|
3019
3191
|
id: "smile",
|
|
3020
3192
|
description: "Smiling and pleased: warm, friendly, content. For greetings, thanks, agreement and gentle happiness.",
|
|
3021
3193
|
parameters: [
|
|
3022
|
-
{ parameter:
|
|
3194
|
+
{ parameter: P5.MouthForm, value: 1, blend: "add" },
|
|
3023
3195
|
...browsY(0.25),
|
|
3024
3196
|
cheek(0.4)
|
|
3025
3197
|
]
|
|
@@ -3028,10 +3200,10 @@ var EXPRESSIONS = [
|
|
|
3028
3200
|
id: "laugh",
|
|
3029
3201
|
description: "Laughing, eyes shut and mouth open: delighted, amused. For jokes, playfulness and big joy.",
|
|
3030
3202
|
parameters: [
|
|
3031
|
-
{ parameter:
|
|
3032
|
-
{ parameter:
|
|
3033
|
-
{ parameter:
|
|
3034
|
-
{ parameter:
|
|
3203
|
+
{ parameter: P5.EyeOpenLeft, value: 0, blend: "multiply" },
|
|
3204
|
+
{ parameter: P5.EyeOpenRight, value: 0, blend: "multiply" },
|
|
3205
|
+
{ parameter: P5.MouthForm, value: 1, blend: "add" },
|
|
3206
|
+
{ parameter: P5.MouthOpen, value: 0.8, blend: "overwrite" },
|
|
3035
3207
|
...browsY(0.4),
|
|
3036
3208
|
cheek(0.65)
|
|
3037
3209
|
]
|
|
@@ -3043,7 +3215,7 @@ var EXPRESSIONS = [
|
|
|
3043
3215
|
parameters: [
|
|
3044
3216
|
...browsY(-0.5),
|
|
3045
3217
|
...browsTilt(0.8),
|
|
3046
|
-
{ parameter:
|
|
3218
|
+
{ parameter: P5.MouthForm, value: -0.9, blend: "add" }
|
|
3047
3219
|
]
|
|
3048
3220
|
},
|
|
3049
3221
|
{
|
|
@@ -3053,7 +3225,7 @@ var EXPRESSIONS = [
|
|
|
3053
3225
|
parameters: [
|
|
3054
3226
|
...browsY(0.35),
|
|
3055
3227
|
...browsTilt(-0.8),
|
|
3056
|
-
{ parameter:
|
|
3228
|
+
{ parameter: P5.MouthForm, value: -0.8, blend: "add" }
|
|
3057
3229
|
]
|
|
3058
3230
|
},
|
|
3059
3231
|
{
|
|
@@ -3061,18 +3233,18 @@ var EXPRESSIONS = [
|
|
|
3061
3233
|
description: "Surprised, mouth dropped open: startled, amazed. For shock, sudden news or disbelief.",
|
|
3062
3234
|
parameters: [
|
|
3063
3235
|
...browsY(0.8),
|
|
3064
|
-
{ parameter:
|
|
3236
|
+
{ parameter: P5.MouthOpen, value: 0.9, blend: "overwrite" }
|
|
3065
3237
|
]
|
|
3066
3238
|
},
|
|
3067
3239
|
{
|
|
3068
3240
|
id: "shy",
|
|
3069
3241
|
description: "Shy or embarrassed: bashful, flustered. For being praised, teased or caught off guard.",
|
|
3070
|
-
needs: [
|
|
3242
|
+
needs: [P5.Cheek],
|
|
3071
3243
|
parameters: [
|
|
3072
3244
|
cheek(1),
|
|
3073
3245
|
// Gaze down.
|
|
3074
|
-
{ parameter:
|
|
3075
|
-
{ parameter:
|
|
3246
|
+
{ parameter: P5.EyeballY, value: -0.65, blend: "add" },
|
|
3247
|
+
{ parameter: P5.MouthForm, value: 0.4, blend: "add" },
|
|
3076
3248
|
...browsTilt(-0.25)
|
|
3077
3249
|
]
|
|
3078
3250
|
}
|
|
@@ -3085,14 +3257,14 @@ function defaultExpressions(declared) {
|
|
|
3085
3257
|
parameters: e.parameters.filter((t) => declared.has(t.parameter))
|
|
3086
3258
|
}));
|
|
3087
3259
|
}
|
|
3088
|
-
var
|
|
3260
|
+
var HEAD_MOTIONS = {
|
|
3089
3261
|
Nod: [
|
|
3090
3262
|
{
|
|
3091
3263
|
description: "Nods yes: agreement, acknowledgement, understanding.",
|
|
3092
3264
|
duration: 1,
|
|
3093
3265
|
curves: [
|
|
3094
3266
|
{
|
|
3095
|
-
parameter:
|
|
3267
|
+
parameter: P5.AngleY,
|
|
3096
3268
|
keys: [
|
|
3097
3269
|
[0, 0],
|
|
3098
3270
|
[0.3, -18],
|
|
@@ -3113,7 +3285,7 @@ var DEFAULT_MOTIONS = {
|
|
|
3113
3285
|
fadeIn: 0.15,
|
|
3114
3286
|
curves: [
|
|
3115
3287
|
{
|
|
3116
|
-
parameter:
|
|
3288
|
+
parameter: P5.AngleX,
|
|
3117
3289
|
keys: [
|
|
3118
3290
|
[0, 0],
|
|
3119
3291
|
[0.2, -18],
|
|
@@ -3132,7 +3304,7 @@ var DEFAULT_MOTIONS = {
|
|
|
3132
3304
|
duration: 1.4,
|
|
3133
3305
|
curves: [
|
|
3134
3306
|
{
|
|
3135
|
-
parameter:
|
|
3307
|
+
parameter: P5.AngleZ,
|
|
3136
3308
|
keys: [
|
|
3137
3309
|
[0, 0],
|
|
3138
3310
|
[0.35, 20],
|
|
@@ -3144,6 +3316,55 @@ var DEFAULT_MOTIONS = {
|
|
|
3144
3316
|
}
|
|
3145
3317
|
]
|
|
3146
3318
|
};
|
|
3319
|
+
var WAVE = {
|
|
3320
|
+
Wave: [
|
|
3321
|
+
{
|
|
3322
|
+
description: "Waves hello with the right hand: greeting, goodbye, getting attention.",
|
|
3323
|
+
duration: 2.2,
|
|
3324
|
+
fadeIn: 0.15,
|
|
3325
|
+
fadeOut: 0.15,
|
|
3326
|
+
curves: [
|
|
3327
|
+
{
|
|
3328
|
+
parameter: P5.ArmPoseRight,
|
|
3329
|
+
interpolation: "linear",
|
|
3330
|
+
keys: [
|
|
3331
|
+
[0, 0],
|
|
3332
|
+
[0.15, 1],
|
|
3333
|
+
[2.05, 1],
|
|
3334
|
+
[2.2, 0]
|
|
3335
|
+
]
|
|
3336
|
+
},
|
|
3337
|
+
{
|
|
3338
|
+
parameter: P5.ArmRight,
|
|
3339
|
+
keys: [
|
|
3340
|
+
[0, 0],
|
|
3341
|
+
[0.4, 10],
|
|
3342
|
+
[1.8, 10],
|
|
3343
|
+
[2.2, 0]
|
|
3344
|
+
]
|
|
3345
|
+
},
|
|
3346
|
+
{
|
|
3347
|
+
parameter: P5.ArmPoseAngleRight,
|
|
3348
|
+
keys: [
|
|
3349
|
+
[0, 0],
|
|
3350
|
+
[0.5, 0],
|
|
3351
|
+
[0.7, 12],
|
|
3352
|
+
[1, -12],
|
|
3353
|
+
[1.3, 12],
|
|
3354
|
+
[1.7, 0],
|
|
3355
|
+
[2.2, 0]
|
|
3356
|
+
]
|
|
3357
|
+
}
|
|
3358
|
+
]
|
|
3359
|
+
}
|
|
3360
|
+
]
|
|
3361
|
+
};
|
|
3362
|
+
function defaultMotions(declared) {
|
|
3363
|
+
const waves = [P5.ArmRight, P5.ArmPoseRight, P5.ArmPoseAngleRight].every(
|
|
3364
|
+
(id) => declared.has(id)
|
|
3365
|
+
);
|
|
3366
|
+
return waves ? { ...HEAD_MOTIONS, ...WAVE } : HEAD_MOTIONS;
|
|
3367
|
+
}
|
|
3147
3368
|
|
|
3148
3369
|
// src/auto-rig/checks.ts
|
|
3149
3370
|
var finite = (v) => typeof v === "number" && Number.isFinite(v);
|
|
@@ -3248,7 +3469,16 @@ function checkLayers(layers, canvas) {
|
|
|
3248
3469
|
if (missing.length > 0) {
|
|
3249
3470
|
throw new Error(`auto-rig: missing required role(s) ${missing.join(", ")}`);
|
|
3250
3471
|
}
|
|
3472
|
+
checkMouth(seen);
|
|
3473
|
+
for (const l of layers) {
|
|
3474
|
+
if (l.rowRuns === void 0 && isLipRole(l.role)) {
|
|
3475
|
+
throw new Error(
|
|
3476
|
+
`auto-rig: layer "${l.fileName}": the lip set needs rowRuns (the fold reads the opening's rows off them; measure with createLayerSetMeasurer)`
|
|
3477
|
+
);
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3251
3480
|
checkArmsHaveBody(seen);
|
|
3481
|
+
checkPosesHaveArms(seen);
|
|
3252
3482
|
}
|
|
3253
3483
|
function checkHeadEdges(edges, byRole) {
|
|
3254
3484
|
if (edges === void 0) return;
|
|
@@ -3366,7 +3596,182 @@ function checkStrandEdges(strand, irises, hair) {
|
|
|
3366
3596
|
}
|
|
3367
3597
|
|
|
3368
3598
|
// src/auto-rig/context.ts
|
|
3369
|
-
import { StandardParameter as
|
|
3599
|
+
import { StandardParameter as P7 } from "@ikijs/format";
|
|
3600
|
+
|
|
3601
|
+
// src/auto-rig/mouth.ts
|
|
3602
|
+
import {
|
|
3603
|
+
StandardParameter as P6
|
|
3604
|
+
} from "@ikijs/format";
|
|
3605
|
+
var UPPER_SHARE = 0.3;
|
|
3606
|
+
var MOUTH_KNOT_PX = 4;
|
|
3607
|
+
var isLipSet = (byRole) => LIP_ROLES.every((r) => byRole.has(r));
|
|
3608
|
+
function columnRuns(layer) {
|
|
3609
|
+
const cols = /* @__PURE__ */ new Map();
|
|
3610
|
+
(layer.rowRuns ?? []).forEach((runs, k) => {
|
|
3611
|
+
for (let i = 0; i < runs.length; i += 2) {
|
|
3612
|
+
for (let c = runs[i]; c < runs[i + 1]; c++) {
|
|
3613
|
+
const list = cols.get(c) ?? [];
|
|
3614
|
+
const last = list[list.length - 1];
|
|
3615
|
+
if (last !== void 0 && last[1] === k) last[1] = k + 1;
|
|
3616
|
+
else list.push([k, k + 1]);
|
|
3617
|
+
cols.set(c, list);
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
});
|
|
3621
|
+
return cols;
|
|
3622
|
+
}
|
|
3623
|
+
var edgesOf = (layer, run) => ({
|
|
3624
|
+
top: layer.canvasH / 2 - (layer.bbox.y + run[0]),
|
|
3625
|
+
bottom: layer.canvasH / 2 - (layer.bbox.y + run[1])
|
|
3626
|
+
});
|
|
3627
|
+
function mouthOpening(byRole) {
|
|
3628
|
+
const inner = byRole.get("mouth_inner");
|
|
3629
|
+
const upper = byRole.get("lip_upper");
|
|
3630
|
+
const lower = byRole.get("lip_lower");
|
|
3631
|
+
const innerCols = columnRuns(inner);
|
|
3632
|
+
if (innerCols.size === 0) {
|
|
3633
|
+
throw new LayerGeometryError(
|
|
3634
|
+
`auto-rig: layer "${inner.fileName}": mouth_inner has no opaque pixel (alpha \u2265 128) to read the opening off`
|
|
3635
|
+
);
|
|
3636
|
+
}
|
|
3637
|
+
const upperCols = columnRuns(upper);
|
|
3638
|
+
const lowerCols = columnRuns(lower);
|
|
3639
|
+
const cols = [...innerCols.keys()];
|
|
3640
|
+
const x0 = Math.min(...cols);
|
|
3641
|
+
const x1 = Math.max(...cols);
|
|
3642
|
+
const read = (col) => {
|
|
3643
|
+
const runs = innerCols.get(col);
|
|
3644
|
+
if (runs === void 0) return void 0;
|
|
3645
|
+
const T = edgesOf(inner, runs[0]).top;
|
|
3646
|
+
const Bb = edgesOf(inner, runs[runs.length - 1]).bottom;
|
|
3647
|
+
const line = [...upperCols.get(col) ?? []].reverse().map((r) => edgesOf(upper, r)).find((e) => e.top >= T);
|
|
3648
|
+
const Tu = line?.bottom ?? T;
|
|
3649
|
+
const lineTop = line?.top ?? T;
|
|
3650
|
+
const skin = lowerCols.get(col)?.[0];
|
|
3651
|
+
return {
|
|
3652
|
+
T,
|
|
3653
|
+
Bb,
|
|
3654
|
+
Tu,
|
|
3655
|
+
lineTop,
|
|
3656
|
+
lineH: lineTop - Tu,
|
|
3657
|
+
Bl: skin === void 0 ? Bb : edgesOf(lower, skin).top,
|
|
3658
|
+
H: T - Bb,
|
|
3659
|
+
seam: T - UPPER_SHARE * (T - Bb)
|
|
3660
|
+
};
|
|
3661
|
+
};
|
|
3662
|
+
const reads = [];
|
|
3663
|
+
for (let c = x0; c <= x1; c++) {
|
|
3664
|
+
let r;
|
|
3665
|
+
for (let d = 0; (r = read(c - d) ?? read(c + d)) === void 0; d++) ;
|
|
3666
|
+
reads.push(r);
|
|
3667
|
+
}
|
|
3668
|
+
const base = (col) => reads[Math.min(x1, Math.max(x0, col)) - x0];
|
|
3669
|
+
const centre = Math.round((x0 + x1) / 2);
|
|
3670
|
+
const w = base(centre).lineH;
|
|
3671
|
+
return {
|
|
3672
|
+
x0,
|
|
3673
|
+
x1,
|
|
3674
|
+
centre,
|
|
3675
|
+
canvasW: inner.canvasW,
|
|
3676
|
+
w,
|
|
3677
|
+
at(col) {
|
|
3678
|
+
const b = base(col);
|
|
3679
|
+
return { ...b, overlap: Math.max(0, Math.min(w, b.lineH)) };
|
|
3680
|
+
}
|
|
3681
|
+
};
|
|
3682
|
+
}
|
|
3683
|
+
function mouthFold(role, opening) {
|
|
3684
|
+
return (col, y, v) => {
|
|
3685
|
+
if (v === 1) return 0;
|
|
3686
|
+
if (role !== "mouth_inner" && (col < opening.x0 || col > opening.x1))
|
|
3687
|
+
return 0;
|
|
3688
|
+
const c = opening.at(col);
|
|
3689
|
+
const k = 1 - v;
|
|
3690
|
+
switch (role) {
|
|
3691
|
+
case "lip_upper":
|
|
3692
|
+
return (c.seam - c.Tu) * k;
|
|
3693
|
+
case "lip_lower":
|
|
3694
|
+
return (c.seam + c.overlap - c.Bl) * k;
|
|
3695
|
+
case "mouth_inner":
|
|
3696
|
+
return (c.seam + c.overlap - y) * k;
|
|
3697
|
+
}
|
|
3698
|
+
};
|
|
3699
|
+
}
|
|
3700
|
+
function columnOfKnot(knot, opening, role) {
|
|
3701
|
+
const boundary = Math.round(knot + opening.canvasW / 2);
|
|
3702
|
+
const col = boundary === opening.x1 + 1 ? opening.x1 : boundary;
|
|
3703
|
+
if (col >= opening.x0 && col <= opening.x1) return col;
|
|
3704
|
+
return role === "mouth_inner" ? Math.min(opening.x1, Math.max(opening.x0, col)) : void 0;
|
|
3705
|
+
}
|
|
3706
|
+
function mouthFoldWarp(role, mesh, xs, box, opening) {
|
|
3707
|
+
const fold = mouthFold(role, opening);
|
|
3708
|
+
return localWarp(P6.MouthOpen, mesh, box, [0, 1], (p, v, i) => {
|
|
3709
|
+
const col = columnOfKnot(xs[i % xs.length], opening, role);
|
|
3710
|
+
return [0, col === void 0 ? 0 : fold(col, p[1], v)];
|
|
3711
|
+
});
|
|
3712
|
+
}
|
|
3713
|
+
function mouthKnots(union, opening) {
|
|
3714
|
+
const half = opening.canvasW / 2;
|
|
3715
|
+
const step = MOUTH_KNOT_PX;
|
|
3716
|
+
const xs = [union.x0];
|
|
3717
|
+
while (xs[xs.length - 1] + step < union.x1 - 0.3 * step)
|
|
3718
|
+
xs.push(xs[xs.length - 1] + step);
|
|
3719
|
+
xs.push(union.x1);
|
|
3720
|
+
const fixed = /* @__PURE__ */ new Set([union.x0, union.x1]);
|
|
3721
|
+
for (const x of [opening.x0 - half, opening.x1 + 1 - half]) {
|
|
3722
|
+
fixed.add(x);
|
|
3723
|
+
if (xs.includes(x)) continue;
|
|
3724
|
+
const near = xs.findIndex((v) => Math.abs(v - x) < 0.3 * step);
|
|
3725
|
+
if (near >= 0 && !fixed.has(xs[near])) xs[near] = x;
|
|
3726
|
+
else xs.push(x);
|
|
3727
|
+
}
|
|
3728
|
+
xs.sort((p, q) => p - q);
|
|
3729
|
+
for (const x of xs) {
|
|
3730
|
+
if (!Number.isInteger(x + half)) {
|
|
3731
|
+
throw new Error(
|
|
3732
|
+
`auto-rig: mouth knot ${x} is not an integer canvas boundary`
|
|
3733
|
+
);
|
|
3734
|
+
}
|
|
3735
|
+
}
|
|
3736
|
+
return xs;
|
|
3737
|
+
}
|
|
3738
|
+
function mouthMesh(box, knots) {
|
|
3739
|
+
const xs = [box.x0, ...knots.filter((k) => k > box.x0 && k < box.x1), box.x1];
|
|
3740
|
+
const rows = cellsFor(bh(box), MOUTH_KNOT_PX, 2, 40);
|
|
3741
|
+
const ys = Array.from(
|
|
3742
|
+
{ length: rows + 1 },
|
|
3743
|
+
(_, r) => box.y1 - bh(box) * r / rows
|
|
3744
|
+
);
|
|
3745
|
+
return { mesh: columnMesh(box, { xs, ys }), xs };
|
|
3746
|
+
}
|
|
3747
|
+
function buildMouthRig(byRole) {
|
|
3748
|
+
const opening = mouthOpening(byRole);
|
|
3749
|
+
const union = unionBoxes(LIP_ROLES.map((r) => boxOfLayer(byRole.get(r))));
|
|
3750
|
+
return {
|
|
3751
|
+
opening,
|
|
3752
|
+
frame: mouthFrameOf(union),
|
|
3753
|
+
knots: mouthKnots(union, opening),
|
|
3754
|
+
union
|
|
3755
|
+
};
|
|
3756
|
+
}
|
|
3757
|
+
function mouthAnchor(byRole) {
|
|
3758
|
+
const box = (role) => boxOfLayer(byRole.get(role));
|
|
3759
|
+
if (isLipSet(byRole)) {
|
|
3760
|
+
const opening = mouthOpening(byRole);
|
|
3761
|
+
return {
|
|
3762
|
+
at: {
|
|
3763
|
+
x: cx(box("mouth_inner")),
|
|
3764
|
+
y: opening.at(opening.centre).seam
|
|
3765
|
+
},
|
|
3766
|
+
box: unionBoxes(LIP_ROLES.map(box))
|
|
3767
|
+
};
|
|
3768
|
+
}
|
|
3769
|
+
const m = box("mouth");
|
|
3770
|
+
return {
|
|
3771
|
+
at: { x: cx(m), y: cy(m) },
|
|
3772
|
+
box: byRole.has("mouth_open") ? unionBoxes([m, box("mouth_open")]) : m
|
|
3773
|
+
};
|
|
3774
|
+
}
|
|
3370
3775
|
|
|
3371
3776
|
// src/auto-rig/head.ts
|
|
3372
3777
|
function outerEdge(runs, axisX, side) {
|
|
@@ -3446,7 +3851,7 @@ function buildHeadFrame(layers, opts) {
|
|
|
3446
3851
|
return ya === void 0 || yb === void 0 ? void 0 : (ya + yb) / 2;
|
|
3447
3852
|
};
|
|
3448
3853
|
const eyeY = pair("iris_L", "iris_R") ?? pair("eye_L", "eye_R");
|
|
3449
|
-
const mouthY =
|
|
3854
|
+
const mouthY = mouthAnchor(byRole).at.y;
|
|
3450
3855
|
const rowY = (r) => canvasH / 2 - (faceLayer.bbox.y + r + 0.5);
|
|
3451
3856
|
const raw = filledProfile(faceLayer.rowHalfWidths);
|
|
3452
3857
|
const profile = raw === null ? null : smoothProfile(raw);
|
|
@@ -4398,6 +4803,7 @@ function familyField(m, family) {
|
|
|
4398
4803
|
return hairField(m, m.hairBack, NOD.hairBack);
|
|
4399
4804
|
case "body":
|
|
4400
4805
|
case "arm":
|
|
4806
|
+
case "forearm_pose":
|
|
4401
4807
|
return () => [0, 0];
|
|
4402
4808
|
}
|
|
4403
4809
|
}
|
|
@@ -4418,7 +4824,6 @@ function sideIrises(byRole) {
|
|
|
4418
4824
|
return pair === void 0 ? {} : { left: pair.left.box, right: pair.right.box };
|
|
4419
4825
|
}
|
|
4420
4826
|
function solveContext(frame, byRole, options, hasNose) {
|
|
4421
|
-
const box = (role) => boxOfLayer(byRole.get(role));
|
|
4422
4827
|
const row = (s, strand2, sideSign) => {
|
|
4423
4828
|
if (strand2 !== void 0) {
|
|
4424
4829
|
const lo = sideSign < 0 ? strand2.irisOuter : strand2.irisInner;
|
|
@@ -4467,8 +4872,7 @@ function solveContext(frame, byRole, options, hasNose) {
|
|
|
4467
4872
|
noseBox = nose.denseCore ? boxOfPixels(nose.denseCore, nose.canvasW, nose.canvasH) : boxOfLayer(nose);
|
|
4468
4873
|
noseAt = { x: cx(noseBox), y: cy(noseBox) };
|
|
4469
4874
|
}
|
|
4470
|
-
const
|
|
4471
|
-
const m = box("mouth");
|
|
4875
|
+
const mouth = mouthAnchor(byRole);
|
|
4472
4876
|
return {
|
|
4473
4877
|
frame,
|
|
4474
4878
|
left,
|
|
@@ -4480,8 +4884,8 @@ function solveContext(frame, byRole, options, hasNose) {
|
|
|
4480
4884
|
eyeBoxes: { left: eyes.left.box, right: eyes.right.box },
|
|
4481
4885
|
noseAt,
|
|
4482
4886
|
noseBox,
|
|
4483
|
-
mouthAt:
|
|
4484
|
-
mouthBox
|
|
4887
|
+
mouthAt: mouth.at,
|
|
4888
|
+
mouthBox: mouth.box
|
|
4485
4889
|
};
|
|
4486
4890
|
}
|
|
4487
4891
|
function renderedLander(turn, parts, grids, from = /* @__PURE__ */ new Map()) {
|
|
@@ -4497,7 +4901,7 @@ function renderedLander(turn, parts, grids, from = /* @__PURE__ */ new Map()) {
|
|
|
4497
4901
|
const family = roleSpec(role).family;
|
|
4498
4902
|
const part = parts.find((p) => p.id === role);
|
|
4499
4903
|
const g = grids.get(family);
|
|
4500
|
-
const warp = part?.warps?.find((w) => w.parameter ===
|
|
4904
|
+
const warp = part?.warps?.find((w) => w.parameter === P7.AngleX);
|
|
4501
4905
|
if (part === void 0 || part.mesh === void 0 || !g && !warp) {
|
|
4502
4906
|
return x + field(role)(x, y, ax, 0)[0];
|
|
4503
4907
|
}
|
|
@@ -5233,6 +5637,7 @@ var WARP_ID = {
|
|
|
5233
5637
|
};
|
|
5234
5638
|
var isGridFamily = (f) => f in WARP_ID;
|
|
5235
5639
|
var isArm = (spec) => spec.family === "arm";
|
|
5640
|
+
var isPose = (spec) => spec.family === "forearm_pose";
|
|
5236
5641
|
var GRID_PAD = 6;
|
|
5237
5642
|
function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
5238
5643
|
checkLayers(layers, canvas);
|
|
@@ -5293,6 +5698,7 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
5293
5698
|
hh: frame.hh,
|
|
5294
5699
|
pivots: [...arms.values()].map((g) => g.shoulder)
|
|
5295
5700
|
}) : void 0;
|
|
5701
|
+
const lips = isLipSet(byRole) ? buildMouthRig(byRole) : void 0;
|
|
5296
5702
|
const parts = [];
|
|
5297
5703
|
const reach = /* @__PURE__ */ new Map();
|
|
5298
5704
|
let regionOf = [];
|
|
@@ -5301,11 +5707,22 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
5301
5707
|
const layer = byRole.get(spec.role);
|
|
5302
5708
|
if (layer === void 0) continue;
|
|
5303
5709
|
if (isArm(spec)) {
|
|
5710
|
+
const pose = POSE_OF_ARM[spec.role];
|
|
5304
5711
|
parts.push(
|
|
5305
|
-
...armParts(
|
|
5712
|
+
...armParts(
|
|
5713
|
+
spec.role,
|
|
5714
|
+
layer,
|
|
5715
|
+
arms.get(spec.role),
|
|
5716
|
+
parts.length,
|
|
5717
|
+
has(pose) ? POSE_PARAMS[pose].pose : void 0
|
|
5718
|
+
)
|
|
5306
5719
|
);
|
|
5307
5720
|
continue;
|
|
5308
5721
|
}
|
|
5722
|
+
if (isPose(spec)) {
|
|
5723
|
+
parts.push(forearmPosePart(spec.role, layer, parts.length));
|
|
5724
|
+
continue;
|
|
5725
|
+
}
|
|
5309
5726
|
const built = buildPart(
|
|
5310
5727
|
spec,
|
|
5311
5728
|
boxOfLayer(layer),
|
|
@@ -5313,7 +5730,8 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
5313
5730
|
frame,
|
|
5314
5731
|
byRole,
|
|
5315
5732
|
style,
|
|
5316
|
-
landmarks.hairFrontGrid
|
|
5733
|
+
landmarks.hairFrontGrid,
|
|
5734
|
+
lips
|
|
5317
5735
|
);
|
|
5318
5736
|
if (built.region !== void 0) {
|
|
5319
5737
|
regionOf = built.region;
|
|
@@ -5358,7 +5776,7 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
5358
5776
|
part.mesh = bodyWarp.mesh;
|
|
5359
5777
|
continue;
|
|
5360
5778
|
}
|
|
5361
|
-
if (family === "arm") continue;
|
|
5779
|
+
if (family === "arm" || family === "forearm_pose") continue;
|
|
5362
5780
|
part.deformer = "headDeformer";
|
|
5363
5781
|
if (part.mesh === void 0) continue;
|
|
5364
5782
|
const own = familyField(turn, family);
|
|
@@ -5375,10 +5793,10 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
5375
5793
|
canvas: { width: canvas.width, height: canvas.height },
|
|
5376
5794
|
parameters,
|
|
5377
5795
|
parts,
|
|
5378
|
-
deformers: deformers(frame, grids, bodyWarp?.deformer, arms),
|
|
5796
|
+
deformers: deformers(frame, grids, bodyWarp?.deformer, arms, byRole),
|
|
5379
5797
|
...has("hair_front") ? { physics: hairPhysics() } : {},
|
|
5380
5798
|
expressions: defaultExpressions(new Set(parameters.map((p) => p.id))),
|
|
5381
|
-
motions:
|
|
5799
|
+
motions: defaultMotions(new Set(parameters.map((p) => p.id)))
|
|
5382
5800
|
};
|
|
5383
5801
|
const valid = parseIkiModel3(model);
|
|
5384
5802
|
if (hasNose && options.onTurnSolved) {
|
|
@@ -5395,14 +5813,14 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
5395
5813
|
function turnWarps(mesh, b, fieldOf) {
|
|
5396
5814
|
return [
|
|
5397
5815
|
localWarp(
|
|
5398
|
-
|
|
5816
|
+
P8.AngleX,
|
|
5399
5817
|
mesh,
|
|
5400
5818
|
b,
|
|
5401
5819
|
X_STOPS,
|
|
5402
5820
|
([x, y], v, i) => v === 0 ? [0, 0] : [fieldOf(i)(x, y, v, 0)[0], 0]
|
|
5403
5821
|
),
|
|
5404
5822
|
localWarp(
|
|
5405
|
-
|
|
5823
|
+
P8.AngleY,
|
|
5406
5824
|
mesh,
|
|
5407
5825
|
b,
|
|
5408
5826
|
Y_STOPS,
|
|
@@ -5415,7 +5833,7 @@ function plateFields(turn, region, head) {
|
|
|
5415
5833
|
const neck = neckField(turn);
|
|
5416
5834
|
return (i) => region[i] === "ear" ? ear : region[i] === "neck" ? neck : head;
|
|
5417
5835
|
}
|
|
5418
|
-
function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
5836
|
+
function buildPart(spec, b, order, frame, byRole, style, frontGrid, lips) {
|
|
5419
5837
|
const box = (role) => boxOfLayer(byRole.get(role));
|
|
5420
5838
|
const part = {
|
|
5421
5839
|
id: spec.role,
|
|
@@ -5430,6 +5848,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5430
5848
|
const bindings = [];
|
|
5431
5849
|
let extra = [0, 0];
|
|
5432
5850
|
let mesh;
|
|
5851
|
+
let knotXs;
|
|
5433
5852
|
let region;
|
|
5434
5853
|
let headStart = 0;
|
|
5435
5854
|
if (spec.role === "face") {
|
|
@@ -5440,6 +5859,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5440
5859
|
headStart = fm.headStart;
|
|
5441
5860
|
} else if (spec.role === "hair_front") {
|
|
5442
5861
|
mesh = columnMesh(b, frontGrid);
|
|
5862
|
+
} else if (lips !== void 0 && isLipRole(spec.role)) {
|
|
5863
|
+
({ mesh, xs: knotXs } = mouthMesh(b, lips.knots));
|
|
5443
5864
|
} else if (spec.family !== "body") {
|
|
5444
5865
|
const cells = MESH_CELLS[spec.role] ?? FEATURE_MESH_CELLS;
|
|
5445
5866
|
const px = Math.min(cells.px, meshScale(spec.role, frame));
|
|
@@ -5450,7 +5871,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5450
5871
|
}
|
|
5451
5872
|
if (mesh !== void 0) part.mesh = mesh;
|
|
5452
5873
|
const side = spec.role.endsWith("_L") ? "L" : "R";
|
|
5453
|
-
const eyeOpen = side === "L" ?
|
|
5874
|
+
const eyeOpen = side === "L" ? P8.EyeOpenLeft : P8.EyeOpenRight;
|
|
5454
5875
|
const hh = frame.hh;
|
|
5455
5876
|
switch (spec.role) {
|
|
5456
5877
|
case "face":
|
|
@@ -5465,7 +5886,7 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5465
5886
|
headOwnRoll(byRole.has("body"))
|
|
5466
5887
|
),
|
|
5467
5888
|
localWarp(
|
|
5468
|
-
|
|
5889
|
+
P8.Breath,
|
|
5469
5890
|
mesh,
|
|
5470
5891
|
b,
|
|
5471
5892
|
[0, 1],
|
|
@@ -5512,8 +5933,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5512
5933
|
if (byRole.has("mouth_open")) {
|
|
5513
5934
|
warps.push(mouthWiden(mesh, b));
|
|
5514
5935
|
bindings.push(
|
|
5515
|
-
{ parameter:
|
|
5516
|
-
{ parameter:
|
|
5936
|
+
{ parameter: P8.MouthOpen, channel: "opacity", from: 1, to: 0 },
|
|
5937
|
+
{ parameter: P8.MouthOpen, channel: "opacity", from: 1, to: 0 }
|
|
5517
5938
|
);
|
|
5518
5939
|
extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
|
|
5519
5940
|
} else {
|
|
@@ -5523,19 +5944,19 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5523
5944
|
);
|
|
5524
5945
|
bindings.push(
|
|
5525
5946
|
{
|
|
5526
|
-
parameter:
|
|
5947
|
+
parameter: P8.MouthOpen,
|
|
5527
5948
|
channel: "scaleY",
|
|
5528
5949
|
from: 0,
|
|
5529
5950
|
to: roundTo(grow, 0.01)
|
|
5530
5951
|
},
|
|
5531
5952
|
{
|
|
5532
|
-
parameter:
|
|
5953
|
+
parameter: P8.MouthOpen,
|
|
5533
5954
|
channel: "scaleX",
|
|
5534
5955
|
from: 0,
|
|
5535
5956
|
to: roundTo(AMPLITUDE.mouthOpenWidth - 1, 0.01)
|
|
5536
5957
|
},
|
|
5537
5958
|
{
|
|
5538
|
-
parameter:
|
|
5959
|
+
parameter: P8.MouthOpen,
|
|
5539
5960
|
channel: "translateY",
|
|
5540
5961
|
from: 0,
|
|
5541
5962
|
to: -roundTo(grow * bh(b) / 2, 0.01)
|
|
@@ -5547,17 +5968,26 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5547
5968
|
case "mouth_open":
|
|
5548
5969
|
warps.push(mouthForm(mesh, b), mouthOpenGrow(mesh, b));
|
|
5549
5970
|
bindings.push({
|
|
5550
|
-
parameter:
|
|
5971
|
+
parameter: P8.MouthOpen,
|
|
5551
5972
|
channel: "opacity",
|
|
5552
5973
|
from: 0,
|
|
5553
5974
|
to: 1
|
|
5554
5975
|
});
|
|
5555
5976
|
extra = [(AMPLITUDE.mouthOpenWidth - 1) * (bw(b) / 2), 0];
|
|
5556
5977
|
break;
|
|
5978
|
+
case "mouth_inner":
|
|
5979
|
+
case "lip_lower":
|
|
5980
|
+
case "lip_upper":
|
|
5981
|
+
warps.push(
|
|
5982
|
+
mouthFoldWarp(spec.role, mesh, knotXs, b, lips.opening),
|
|
5983
|
+
mouthForm(mesh, b, lips.frame),
|
|
5984
|
+
mouthWiden(mesh, b, lips.frame)
|
|
5985
|
+
);
|
|
5986
|
+
break;
|
|
5557
5987
|
case "blush_L":
|
|
5558
5988
|
case "blush_R":
|
|
5559
5989
|
bindings.push({
|
|
5560
|
-
parameter:
|
|
5990
|
+
parameter: P8.Cheek,
|
|
5561
5991
|
channel: "opacity",
|
|
5562
5992
|
from: AMPLITUDE.blushRest,
|
|
5563
5993
|
to: 1
|
|
@@ -5568,8 +5998,8 @@ function buildPart(spec, b, order, frame, byRole, style, frontGrid) {
|
|
|
5568
5998
|
if (byRole.has("hair_front")) {
|
|
5569
5999
|
const amp = style.sway * AMPLITUDE.sway * hh;
|
|
5570
6000
|
warps.push(
|
|
5571
|
-
hairSway(
|
|
5572
|
-
hairSway(
|
|
6001
|
+
hairSway(P8.HairSwayX, mesh, b, amp),
|
|
6002
|
+
hairSway(P8.HairSwayZ, mesh, b, amp)
|
|
5573
6003
|
);
|
|
5574
6004
|
}
|
|
5575
6005
|
warps.push(tiltHang(mesh, b, pivot, hairHangWeight(frame.chinY, b.y0)));
|
|
@@ -5610,29 +6040,6 @@ function hairFrontGrid(b, frame, boxes) {
|
|
|
5610
6040
|
);
|
|
5611
6041
|
return { xs, ys };
|
|
5612
6042
|
}
|
|
5613
|
-
function columnMesh(b, grid) {
|
|
5614
|
-
const { xs } = grid;
|
|
5615
|
-
const rows = grid.ys.length - 1;
|
|
5616
|
-
const vertices = [];
|
|
5617
|
-
const uvs = [];
|
|
5618
|
-
for (let r = 0; r <= rows; r++) {
|
|
5619
|
-
for (const x of xs) {
|
|
5620
|
-
const u = (x - b.x0) / bw(b);
|
|
5621
|
-
vertices.push(roundTo(u - 0.5, 1e-5), roundTo(0.5 - r / rows, 1e-5));
|
|
5622
|
-
uvs.push(roundTo(u, 1e-5), roundTo(r / rows, 1e-5));
|
|
5623
|
-
}
|
|
5624
|
-
}
|
|
5625
|
-
const n = xs.length;
|
|
5626
|
-
const indices = [];
|
|
5627
|
-
for (let r = 0; r < rows; r++) {
|
|
5628
|
-
for (let c = 0; c + 1 < n; c++) {
|
|
5629
|
-
const tl = r * n + c;
|
|
5630
|
-
const bl = tl + n;
|
|
5631
|
-
indices.push(bl, bl + 1, tl, tl, bl + 1, tl + 1);
|
|
5632
|
-
}
|
|
5633
|
-
}
|
|
5634
|
-
return { vertices, uvs, indices };
|
|
5635
|
-
}
|
|
5636
6043
|
function meshScale(role, frame) {
|
|
5637
6044
|
switch (role) {
|
|
5638
6045
|
case "hair_front":
|
|
@@ -5642,10 +6049,14 @@ function meshScale(role, frame) {
|
|
|
5642
6049
|
return Infinity;
|
|
5643
6050
|
}
|
|
5644
6051
|
}
|
|
5645
|
-
function deformers(frame, grids, bodyWarp, arms) {
|
|
6052
|
+
function deformers(frame, grids, bodyWarp, arms, byRole) {
|
|
5646
6053
|
const out = [];
|
|
5647
6054
|
if (bodyWarp !== void 0) out.push(bodyWarp);
|
|
5648
6055
|
for (const [role, g] of arms) out.push(...armDeformers(role, g));
|
|
6056
|
+
for (const [role, arm] of arms) {
|
|
6057
|
+
const pose = POSE_OF_ARM[role];
|
|
6058
|
+
if (byRole.has(pose)) out.push(forearmPoseDeformer(pose, arm));
|
|
6059
|
+
}
|
|
5649
6060
|
const hasBody = bodyWarp !== void 0;
|
|
5650
6061
|
const roll = headOwnRoll(hasBody);
|
|
5651
6062
|
out.push({
|
|
@@ -5654,9 +6065,9 @@ function deformers(frame, grids, bodyWarp, arms) {
|
|
|
5654
6065
|
pivot: { x: roundTo(frame.axisX, 0.01), y: roundTo(frame.chinY, 0.01) },
|
|
5655
6066
|
bindings: [
|
|
5656
6067
|
// AngleZ is clockwise-positive (Live2D's); a rotation is CCW-positive.
|
|
5657
|
-
{ parameter:
|
|
6068
|
+
{ parameter: P8.AngleZ, channel: "rotate", from: roll, to: -roll },
|
|
5658
6069
|
{
|
|
5659
|
-
parameter:
|
|
6070
|
+
parameter: P8.Breath,
|
|
5660
6071
|
channel: "translateY",
|
|
5661
6072
|
from: 0,
|
|
5662
6073
|
to: roundTo(headOwnBreath(hasBody) * frame.hh, 0.01)
|
|
@@ -5674,8 +6085,8 @@ function deformers(frame, grids, bodyWarp, arms) {
|
|
|
5674
6085
|
points: g.lattice.points
|
|
5675
6086
|
},
|
|
5676
6087
|
warp2d: {
|
|
5677
|
-
parameter:
|
|
5678
|
-
parameterY:
|
|
6088
|
+
parameter: P8.AngleX,
|
|
6089
|
+
parameterY: P8.AngleY,
|
|
5679
6090
|
valuesX: X_STOPS,
|
|
5680
6091
|
valuesY: Y_STOPS,
|
|
5681
6092
|
keyforms2d: g.keyforms.map((offsets) => ({ offsets }))
|
|
@@ -5688,16 +6099,16 @@ function hairPhysics() {
|
|
|
5688
6099
|
return [
|
|
5689
6100
|
{
|
|
5690
6101
|
id: "hairSway",
|
|
5691
|
-
input: { parameter:
|
|
5692
|
-
output: { parameter:
|
|
6102
|
+
input: { parameter: P8.AngleX, weight: 1 },
|
|
6103
|
+
output: { parameter: P8.HairSwayX, scale: 5 },
|
|
5693
6104
|
mass: 1,
|
|
5694
6105
|
stiffness: 30,
|
|
5695
6106
|
damping: 3
|
|
5696
6107
|
},
|
|
5697
6108
|
{
|
|
5698
6109
|
id: "hairTilt",
|
|
5699
|
-
input: { parameter:
|
|
5700
|
-
output: { parameter:
|
|
6110
|
+
input: { parameter: P8.AngleZ, weight: 1 },
|
|
6111
|
+
output: { parameter: P8.HairSwayZ, scale: 8 },
|
|
5701
6112
|
mass: 1,
|
|
5702
6113
|
stiffness: 25,
|
|
5703
6114
|
damping: 2.5
|
|
@@ -5707,47 +6118,55 @@ function hairPhysics() {
|
|
|
5707
6118
|
function declareParameters(roles) {
|
|
5708
6119
|
const out = [];
|
|
5709
6120
|
const add = (id, name, min, max, def) => out.push({ id, name, min, max, default: def });
|
|
5710
|
-
add(
|
|
5711
|
-
add(
|
|
5712
|
-
add(
|
|
6121
|
+
add(P8.AngleX, "Head Angle", -30, 30, 0);
|
|
6122
|
+
add(P8.AngleY, "Head Angle Y", -30, 30, 0);
|
|
6123
|
+
add(P8.AngleZ, "Head Angle Z", -30, 30, 0);
|
|
5713
6124
|
if (roles.has("body")) {
|
|
5714
|
-
add(
|
|
5715
|
-
add(
|
|
5716
|
-
add(
|
|
6125
|
+
add(P8.BodyAngleX, "Body Angle X", -10, 10, 0);
|
|
6126
|
+
add(P8.BodyAngleY, "Body Angle Y", -10, 10, 0);
|
|
6127
|
+
add(P8.BodyAngleZ, "Body Angle Z", -10, 10, 0);
|
|
5717
6128
|
}
|
|
6129
|
+
const addPose = (side, pose, angle) => {
|
|
6130
|
+
add(pose, `Arm Pose ${side}`, 0, 1, 0);
|
|
6131
|
+
add(angle, `Arm Pose Angle ${side}`, ...POSE_ANGLE_RANGE, 0);
|
|
6132
|
+
};
|
|
5718
6133
|
const [armMin, armMax] = ARM_RANGE;
|
|
5719
6134
|
const [elbowMin, elbowMax] = ELBOW_RANGE;
|
|
5720
6135
|
if (roles.has("arm_L")) {
|
|
5721
|
-
add(
|
|
5722
|
-
add(
|
|
6136
|
+
add(P8.ArmLeft, "Arm L", armMin, armMax, 0);
|
|
6137
|
+
add(P8.ElbowLeft, "Elbow L", elbowMin, elbowMax, 0);
|
|
6138
|
+
if (roles.has("forearm_pose_L"))
|
|
6139
|
+
addPose("L", P8.ArmPoseLeft, P8.ArmPoseAngleLeft);
|
|
5723
6140
|
}
|
|
5724
6141
|
if (roles.has("arm_R")) {
|
|
5725
|
-
add(
|
|
5726
|
-
add(
|
|
6142
|
+
add(P8.ArmRight, "Arm R", armMin, armMax, 0);
|
|
6143
|
+
add(P8.ElbowRight, "Elbow R", elbowMin, elbowMax, 0);
|
|
6144
|
+
if (roles.has("forearm_pose_R"))
|
|
6145
|
+
addPose("R", P8.ArmPoseRight, P8.ArmPoseAngleRight);
|
|
5727
6146
|
}
|
|
5728
|
-
add(
|
|
5729
|
-
add(
|
|
6147
|
+
add(P8.EyeOpenLeft, "Eye L", 0, 1, 1);
|
|
6148
|
+
add(P8.EyeOpenRight, "Eye R", 0, 1, 1);
|
|
5730
6149
|
if ([...roles].some((r) => /^(iris|pupil|highlight)_/.test(r))) {
|
|
5731
|
-
add(
|
|
5732
|
-
add(
|
|
6150
|
+
add(P8.EyeballX, "Gaze X", -1, 1, 0);
|
|
6151
|
+
add(P8.EyeballY, "Gaze Y", -1, 1, 0);
|
|
5733
6152
|
}
|
|
5734
|
-
add(
|
|
5735
|
-
add(
|
|
6153
|
+
add(P8.MouthOpen, "Mouth Open", 0, 1, 0);
|
|
6154
|
+
add(P8.MouthForm, "Mouth Form", -1, 1, 0);
|
|
5736
6155
|
if (roles.has("brow_L")) {
|
|
5737
|
-
add(
|
|
5738
|
-
add(
|
|
6156
|
+
add(P8.BrowLeftY, "Brow L Y", -1, 1, 0);
|
|
6157
|
+
add(P8.BrowLeftAngle, "Brow L Angle", -1, 1, 0);
|
|
5739
6158
|
}
|
|
5740
6159
|
if (roles.has("brow_R")) {
|
|
5741
|
-
add(
|
|
5742
|
-
add(
|
|
6160
|
+
add(P8.BrowRightY, "Brow R Y", -1, 1, 0);
|
|
6161
|
+
add(P8.BrowRightAngle, "Brow R Angle", -1, 1, 0);
|
|
5743
6162
|
}
|
|
5744
6163
|
if (roles.has("blush_L") || roles.has("blush_R")) {
|
|
5745
|
-
add(
|
|
6164
|
+
add(P8.Cheek, "Cheek", 0, 1, 0);
|
|
5746
6165
|
}
|
|
5747
|
-
add(
|
|
6166
|
+
add(P8.Breath, "Breath", 0, 1, 0);
|
|
5748
6167
|
if (roles.has("hair_front")) {
|
|
5749
|
-
add(
|
|
5750
|
-
add(
|
|
6168
|
+
add(P8.HairSwayX, "Hair Sway X", -SWAY_RANGE, SWAY_RANGE, 0);
|
|
6169
|
+
add(P8.HairSwayZ, "Hair Sway Z", -SWAY_RANGE, SWAY_RANGE, 0);
|
|
5751
6170
|
}
|
|
5752
6171
|
return out;
|
|
5753
6172
|
}
|
|
@@ -5794,6 +6213,7 @@ export {
|
|
|
5794
6213
|
createLayerSetMeasurer,
|
|
5795
6214
|
denseCoreOf,
|
|
5796
6215
|
detectAlphaBbox,
|
|
6216
|
+
forearmPoseGeometry,
|
|
5797
6217
|
foregroundSpan,
|
|
5798
6218
|
generateIkiFromLayerSet,
|
|
5799
6219
|
headHalfOf,
|