@diffenderfer-games/hub 2.0.0-next.10 → 2.0.0-next.11
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/CHANGELOG.md +26 -12
- package/dist/avatar-pixi.js +3 -3
- package/dist/doctor/cli.mjs +67 -4
- package/dist/doctor/cli.mjs.map +1 -1
- package/dist/shared/avatar.d.ts +3 -3
- package/dist/shared/avatar.d.ts.map +1 -1
- package/dist/shared/client-version.gen.js +1 -1
- package/dist/shared/daily.js +1 -1
- package/dist/shared/describe.d.ts.map +1 -1
- package/dist/shared/index.d.ts +1 -1
- package/dist/shared/svg-markup.js +153 -96
- package/dist/shared/svg-markup.js.map +1 -1
- package/package.json +3 -3
- package/src/avatar/adapters/pixi-crowd.ts +2 -2
- package/src/avatar/render/compose.ts +48 -22
- package/src/avatar/render/frame.ts +2 -2
- package/src/avatar/render/walker-parts.ts +24 -21
- package/src/avatar/render/walker.ts +100 -60
- package/src/core/version/client-version.gen.ts +1 -1
- package/src/doctor/checks.ts +2 -2
- package/src/doctor/cli.ts +5 -2
- package/src/doctor/embed-check.ts +61 -0
|
@@ -232,12 +232,12 @@ const FRAMES = {
|
|
|
232
232
|
206
|
|
233
233
|
]
|
|
234
234
|
};
|
|
235
|
-
/** The walker's full frame: a
|
|
235
|
+
/** The walker's full frame: a short, chunky figure, with room for hats, jumps and feet dangling below a seat. */
|
|
236
236
|
const WALKER_FULL = [
|
|
237
237
|
-60,
|
|
238
|
-
-
|
|
238
|
+
-144,
|
|
239
239
|
120,
|
|
240
|
-
|
|
240
|
+
170
|
|
241
241
|
];
|
|
242
242
|
/** True when options draw the walker figure (full frames only). */
|
|
243
243
|
function isWalker(opts) {
|
|
@@ -1650,42 +1650,49 @@ function limb(points, width, fill, ink) {
|
|
|
1650
1650
|
cap: "round"
|
|
1651
1651
|
})];
|
|
1652
1652
|
}
|
|
1653
|
-
/** A shoe at a foot point, nudged along the facing. */
|
|
1653
|
+
/** A shoe at a foot point, nudged along the facing: round and stubby. */
|
|
1654
1654
|
function shoe(at, forward, o) {
|
|
1655
|
-
return ellipse(at[0] + forward[0] * 2.4, at[1] + forward[1] * 1.6 - 1.
|
|
1655
|
+
return ellipse(at[0] + forward[0] * 2.4, at[1] + forward[1] * 1.6 - 1.6, 7.2 + Math.abs(forward[0]) * 1.4, 4.1, {
|
|
1656
1656
|
fill: o.shoes,
|
|
1657
1657
|
stroke: o.shoesInk,
|
|
1658
1658
|
sw: LINE * .8
|
|
1659
1659
|
});
|
|
1660
1660
|
}
|
|
1661
|
-
/** A hand. */
|
|
1661
|
+
/** A hand: a round mitten. */
|
|
1662
1662
|
function hand(c, at) {
|
|
1663
|
-
return ellipse(at[0], at[1],
|
|
1663
|
+
return ellipse(at[0], at[1], 5.2, 5.2, {
|
|
1664
1664
|
fill: c.skin,
|
|
1665
1665
|
stroke: c.skinInk,
|
|
1666
1666
|
sw: LINE * .8
|
|
1667
1667
|
});
|
|
1668
1668
|
}
|
|
1669
|
+
/** The torso's half-widths (before the turn's width factor): square shoulders over a round belly. */
|
|
1670
|
+
const TORSO = {
|
|
1671
|
+
shoulder: 20,
|
|
1672
|
+
belly: 23.5,
|
|
1673
|
+
hip: 21
|
|
1674
|
+
};
|
|
1669
1675
|
/** The torso's outline: shoulders at `top`, the hem at `hem`, `w` its width factor (narrower when turned). */
|
|
1670
1676
|
function torsoPath(w, top, hem) {
|
|
1671
|
-
const shoulder =
|
|
1672
|
-
const
|
|
1673
|
-
const hip =
|
|
1674
|
-
const mid = (top + hem) / 2 +
|
|
1675
|
-
return new Path().m(-shoulder, top +
|
|
1677
|
+
const shoulder = TORSO.shoulder * w;
|
|
1678
|
+
const belly = TORSO.belly * w;
|
|
1679
|
+
const hip = TORSO.hip * w;
|
|
1680
|
+
const mid = (top + hem) / 2 + 2;
|
|
1681
|
+
return new Path().m(-shoulder, top + 5).c(-shoulder, top - 1, -shoulder + 6, top - 3, -shoulder * .35, top - 3).l(shoulder * .35, top - 3).c(shoulder - 6, top - 3, shoulder, top - 1, shoulder, top + 5).c(belly, mid - 4, belly, mid + 4, hip, hem - 1).q(0, hem + 4, -hip, hem - 1).c(-belly, mid + 4, -belly, mid - 4, -shoulder, top + 5).z().d();
|
|
1676
1682
|
}
|
|
1677
1683
|
/** The shirt: a shade base, the lit front, the outline, and the neckline (front) or collar (back). */
|
|
1678
1684
|
function shirt(c, w, top, hem, lean, isBack) {
|
|
1679
1685
|
const d = torsoPath(w, top, hem);
|
|
1680
1686
|
const cy = (top + hem) / 2;
|
|
1687
|
+
const hemHalf = (TORSO.hip - 3) * w;
|
|
1681
1688
|
const nodes = [
|
|
1682
1689
|
path(d, { fill: c.bodyShade }),
|
|
1683
1690
|
g([path(d, { fill: c.body })], { tf: [
|
|
1684
1691
|
tr(-1.5 - lean * 2, cy - 1.5),
|
|
1685
|
-
sc(.
|
|
1692
|
+
sc(.88, .9),
|
|
1686
1693
|
tr(0, -cy)
|
|
1687
1694
|
] }),
|
|
1688
|
-
path(`M${String(-
|
|
1695
|
+
path(`M${String(-hemHalf)} ${String(hem - 2)}Q0 ${String(hem + 1.5)} ${String(hemHalf)} ${String(hem - 2)}`, {
|
|
1689
1696
|
fill: "none",
|
|
1690
1697
|
stroke: shade(c.bodyShade),
|
|
1691
1698
|
sw: 2.2,
|
|
@@ -1703,7 +1710,7 @@ function shirt(c, w, top, hem, lean, isBack) {
|
|
|
1703
1710
|
stroke: c.bodyShade,
|
|
1704
1711
|
sw: 3
|
|
1705
1712
|
}));
|
|
1706
|
-
else nodes.push(path(`M${String(neckX - 6 * w)} ${String(top - 2.6)}Q${String(neckX)} ${String(top +
|
|
1713
|
+
else nodes.push(path(`M${String(neckX - 6 * w)} ${String(top - 2.6)}Q${String(neckX)} ${String(top + 5)} ${String(neckX + 6 * w)} ${String(top - 2.6)}Z`, {
|
|
1707
1714
|
fill: c.skin,
|
|
1708
1715
|
stroke: c.bodyInk,
|
|
1709
1716
|
sw: LINE * .8
|
|
@@ -1712,16 +1719,16 @@ function shirt(c, w, top, hem, lean, isBack) {
|
|
|
1712
1719
|
}
|
|
1713
1720
|
/** The seat of the trousers joining the legs under the hem. */
|
|
1714
1721
|
function hips(o, w, hipY) {
|
|
1715
|
-
const half =
|
|
1722
|
+
const half = 16 * w;
|
|
1716
1723
|
return path(new Path().m(-half, hipY - 4).l(half, hipY - 4).l(half, hipY + 4).q(0, hipY + 9, -half, hipY + 4).z().d(), {
|
|
1717
1724
|
fill: o.trousers,
|
|
1718
1725
|
stroke: o.trousersInk,
|
|
1719
1726
|
sw: LINE * .8
|
|
1720
1727
|
});
|
|
1721
1728
|
}
|
|
1722
|
-
/** The neck under the head. */
|
|
1729
|
+
/** The neck under the head (a short one: the big head sits almost on the shoulders). */
|
|
1723
1730
|
function neck(c, top, dx) {
|
|
1724
|
-
return path(new Path().m(dx -
|
|
1731
|
+
return path(new Path().m(dx - 5, top - 6).l(dx + 5, top - 6).l(dx + 5, top).l(dx - 5, top).z().d(), {
|
|
1725
1732
|
fill: c.skinShade,
|
|
1726
1733
|
stroke: c.skinInk,
|
|
1727
1734
|
sw: LINE * .6
|
|
@@ -1730,37 +1737,44 @@ function neck(c, top, dx) {
|
|
|
1730
1737
|
//#endregion
|
|
1731
1738
|
//#region src/avatar/render/walker.ts
|
|
1732
1739
|
/**
|
|
1733
|
-
* The world walker (`figure: 'walker'`): a small
|
|
1734
|
-
* maps and crowds
|
|
1735
|
-
*
|
|
1736
|
-
* pose names: `walk` and
|
|
1737
|
-
* `
|
|
1738
|
-
* the
|
|
1740
|
+
* The world walker (`figure: 'walker'`): a small, chunky, chibi-like
|
|
1741
|
+
* full-body character for maps and crowds: a short round body with stubby
|
|
1742
|
+
* legs and arms, a shirt, trousers and shoes, and a big head on top
|
|
1743
|
+
* ({@link WALKER_HEAD_SCALE}). It takes the blob's pose names: `walk` and
|
|
1744
|
+
* `run` waddle (quick steps that bounce and rock from foot to foot), `sit`
|
|
1745
|
+
* folds the legs over a seat, `idle` breathes, `wave` and `cheer` lift the
|
|
1746
|
+
* arms.
|
|
1739
1747
|
*
|
|
1740
1748
|
* Standing, the origin is the feet. Sitting (`sit`, `play`), the origin is
|
|
1741
|
-
* the seat: the hips rest on it and the
|
|
1742
|
-
*
|
|
1749
|
+
* the seat: the hips rest on it and the short legs dangle over its front
|
|
1750
|
+
* edge, the feet {@link WALKER_SEAT_DROP} units below it.
|
|
1743
1751
|
*/
|
|
1744
1752
|
/** The head's scale on a walker (the blob draws it at 1). */
|
|
1745
|
-
const WALKER_HEAD_SCALE = .
|
|
1746
|
-
/** How far below the seat a sitter's feet reach (rig units). */
|
|
1747
|
-
const WALKER_SEAT_DROP =
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1753
|
+
const WALKER_HEAD_SCALE = .62;
|
|
1754
|
+
/** How far below the seat a sitter's dangling feet reach (rig units): short legs swing clear of the floor. */
|
|
1755
|
+
const WALKER_SEAT_DROP = 13;
|
|
1756
|
+
/** Standing: a short, round body (half the first walker's 72 units to the shoulders) under the head. */
|
|
1757
|
+
const WALKER_STANDING = {
|
|
1758
|
+
hip: -18,
|
|
1759
|
+
shoulder: -38,
|
|
1760
|
+
head: -70
|
|
1752
1761
|
};
|
|
1753
1762
|
const SEATED = {
|
|
1754
1763
|
hip: -3,
|
|
1755
|
-
shoulder: -
|
|
1756
|
-
head: -
|
|
1764
|
+
shoulder: -23,
|
|
1765
|
+
head: -55
|
|
1757
1766
|
};
|
|
1758
|
-
const LEG =
|
|
1759
|
-
const UPPER_ARM =
|
|
1760
|
-
const FOREARM =
|
|
1761
|
-
const LEG_W =
|
|
1762
|
-
const SLEEVE_W =
|
|
1763
|
-
const ARM_W =
|
|
1767
|
+
const LEG = 18;
|
|
1768
|
+
const UPPER_ARM = 8;
|
|
1769
|
+
const FOREARM = 7;
|
|
1770
|
+
const LEG_W = 13;
|
|
1771
|
+
const SLEEVE_W = 12.5;
|
|
1772
|
+
const ARM_W = 9;
|
|
1773
|
+
/** How far out the hips and shoulders sit from the middle. */
|
|
1774
|
+
const HIP_X = 7.5;
|
|
1775
|
+
const SHOULDER_X = 19;
|
|
1776
|
+
/** The seated thigh, along the facing. */
|
|
1777
|
+
const THIGH = 9;
|
|
1764
1778
|
const DEG$1 = Math.PI / 180;
|
|
1765
1779
|
function viewOf(angleDeg) {
|
|
1766
1780
|
const side = Math.sin(angleDeg * DEG$1);
|
|
@@ -1770,7 +1784,7 @@ function viewOf(angleDeg) {
|
|
|
1770
1784
|
front,
|
|
1771
1785
|
forward: [side, front * .45],
|
|
1772
1786
|
isBack: angleDeg > 90,
|
|
1773
|
-
width: 1 - .
|
|
1787
|
+
width: 1 - .26 * Math.abs(side)
|
|
1774
1788
|
};
|
|
1775
1789
|
}
|
|
1776
1790
|
/** A body side's screen x for a lateral distance (sides overlap when turned). */
|
|
@@ -1778,78 +1792,102 @@ const lateralX = (v, sideOf, distance) => sideOf * distance * v.front;
|
|
|
1778
1792
|
/** How near the viewer a side is (> 0: drawn in front when turned). */
|
|
1779
1793
|
const nearness = (v, sideOf) => -sideOf * v.side;
|
|
1780
1794
|
const add = (a, b, k = 1) => [a[0] + b[0] * k, a[1] + b[1] * k];
|
|
1795
|
+
/** The walk's and run's step: stride angle, foot lift, bounce and side-to-side rock (degrees). */
|
|
1796
|
+
const GAIT = {
|
|
1797
|
+
walk: {
|
|
1798
|
+
stride: 36,
|
|
1799
|
+
lift: 3.6,
|
|
1800
|
+
bounce: 3.2,
|
|
1801
|
+
rock: 5
|
|
1802
|
+
},
|
|
1803
|
+
run: {
|
|
1804
|
+
stride: 46,
|
|
1805
|
+
lift: 5.4,
|
|
1806
|
+
bounce: 4.4,
|
|
1807
|
+
rock: 3.5
|
|
1808
|
+
}
|
|
1809
|
+
};
|
|
1810
|
+
/** The gait a pose steps with, if it steps. */
|
|
1811
|
+
function gaitOf(pose) {
|
|
1812
|
+
return pose === "run" ? GAIT.run : pose === "walk" || pose === "dance" ? GAIT.walk : null;
|
|
1813
|
+
}
|
|
1814
|
+
/** A shoulder joint: out at the torso's edge (narrower when turned), nudged along the facing. */
|
|
1815
|
+
const shoulderAt = (v, h, sideOf, bob) => [lateralX(v, sideOf, SHOULDER_X * v.width) + v.forward[0] * 1.5, h.shoulder + 4 + bob];
|
|
1781
1816
|
function standingLimbs(v, h, pose, phase, bob) {
|
|
1782
|
-
const
|
|
1783
|
-
const
|
|
1784
|
-
const
|
|
1785
|
-
const swingOf = (sideOf) => isWalking ? Math.sin((phase + (sideOf === 1 ? 0 : .5)) * Math.PI * 2) : 0;
|
|
1817
|
+
const gait = gaitOf(pose);
|
|
1818
|
+
const stepOf = (sideOf) => (phase + (sideOf === 1 ? 0 : .5)) * Math.PI * 2;
|
|
1819
|
+
const swingOf = (sideOf) => gait ? Math.sin(stepOf(sideOf)) : 0;
|
|
1786
1820
|
return {
|
|
1787
1821
|
leg: (sideOf) => {
|
|
1788
|
-
const hip = [lateralX(v, sideOf,
|
|
1789
|
-
const
|
|
1790
|
-
const
|
|
1791
|
-
const reach = Math.sin(stride * DEG$1) * LEG * swing;
|
|
1822
|
+
const hip = [lateralX(v, sideOf, HIP_X), h.hip + bob];
|
|
1823
|
+
const lift = gait ? Math.max(0, Math.cos(stepOf(sideOf))) * gait.lift : 0;
|
|
1824
|
+
const reach = gait ? Math.sin(gait.stride * DEG$1) * LEG * swingOf(sideOf) : 0;
|
|
1792
1825
|
const foot = [hip[0] + v.forward[0] * reach, -bob * .2 + v.forward[1] * reach - lift];
|
|
1793
1826
|
return [
|
|
1794
1827
|
hip,
|
|
1795
|
-
[(hip[0] + foot[0]) / 2 + v.forward[0] * lift
|
|
1828
|
+
[(hip[0] + foot[0]) / 2 + v.forward[0] * lift, (hip[1] + foot[1]) / 2 + v.forward[1] * lift],
|
|
1796
1829
|
foot
|
|
1797
1830
|
];
|
|
1798
1831
|
},
|
|
1799
1832
|
arm: (sideOf) => {
|
|
1800
|
-
const shoulder =
|
|
1801
|
-
const out = sideOf * v.front *
|
|
1833
|
+
const shoulder = shoulderAt(v, h, sideOf, bob);
|
|
1834
|
+
const out = sideOf * v.front * 3.4;
|
|
1802
1835
|
if (pose === "cheer" || pose === "wave" && sideOf === -1) {
|
|
1803
|
-
const wave = pose === "wave" ? Math.sin(phase * Math.PI * 2) *
|
|
1804
|
-
const elbow = [shoulder[0] + out *
|
|
1836
|
+
const wave = pose === "wave" ? Math.sin(phase * Math.PI * 2) * 3 : 0;
|
|
1837
|
+
const elbow = [shoulder[0] + out * 2.2, shoulder[1] - 7];
|
|
1805
1838
|
return [
|
|
1806
1839
|
shoulder,
|
|
1807
1840
|
elbow,
|
|
1808
|
-
[elbow[0] + out *
|
|
1841
|
+
[elbow[0] + out * .8 + wave, elbow[1] - 10]
|
|
1809
1842
|
];
|
|
1810
1843
|
}
|
|
1811
|
-
const swing =
|
|
1812
|
-
const reach = Math.sin(
|
|
1844
|
+
const swing = gait ? -swingOf(sideOf) * (pose === "run" ? 1.4 : 1.1) : Math.sin(phase * Math.PI * 2) * .06;
|
|
1845
|
+
const reach = Math.sin(30 * DEG$1) * swing;
|
|
1813
1846
|
const elbow = [shoulder[0] + out + v.forward[0] * UPPER_ARM * reach * .6, shoulder[1] + UPPER_ARM + v.forward[1] * UPPER_ARM * reach];
|
|
1814
1847
|
return [
|
|
1815
1848
|
shoulder,
|
|
1816
1849
|
elbow,
|
|
1817
|
-
[elbow[0] + out * .
|
|
1850
|
+
[elbow[0] + out * .35 + v.forward[0] * FOREARM * reach * 1.6, elbow[1] + FOREARM - Math.abs(reach) * 2 + v.forward[1] * FOREARM * reach]
|
|
1818
1851
|
];
|
|
1819
1852
|
}
|
|
1820
1853
|
};
|
|
1821
1854
|
}
|
|
1822
1855
|
function seatedLimbs(v, h, pose) {
|
|
1823
|
-
const thigh = [v.forward[0] *
|
|
1856
|
+
const thigh = [v.forward[0] * THIGH, v.forward[1] * 10];
|
|
1824
1857
|
return {
|
|
1825
1858
|
leg: (sideOf) => {
|
|
1826
|
-
const hip = [lateralX(v, sideOf,
|
|
1859
|
+
const hip = [lateralX(v, sideOf, HIP_X), h.hip];
|
|
1827
1860
|
const knee = add(hip, thigh);
|
|
1828
1861
|
return [
|
|
1829
1862
|
hip,
|
|
1830
1863
|
knee,
|
|
1831
|
-
[knee[0] + v.forward[0] *
|
|
1864
|
+
[knee[0] + v.forward[0] * 2, 11]
|
|
1832
1865
|
];
|
|
1833
1866
|
},
|
|
1834
1867
|
arm: (sideOf) => {
|
|
1835
|
-
const shoulder =
|
|
1868
|
+
const shoulder = shoulderAt(v, h, sideOf, 0);
|
|
1836
1869
|
return [
|
|
1837
1870
|
shoulder,
|
|
1838
|
-
[shoulder[0] + sideOf * v.front *
|
|
1839
|
-
add([lateralX(v, sideOf, pose === "play" ? 4 :
|
|
1871
|
+
[shoulder[0] + sideOf * v.front * 3 + v.forward[0] * 2, shoulder[1] + UPPER_ARM],
|
|
1872
|
+
add([lateralX(v, sideOf, pose === "play" ? 4 : 8), h.hip - 3], thigh, pose === "play" ? .9 : .6)
|
|
1840
1873
|
];
|
|
1841
1874
|
}
|
|
1842
1875
|
};
|
|
1843
1876
|
}
|
|
1877
|
+
/** The long-hair split in head space: a little above the torso's top edge (the first walker's line sat 12 head units above it). */
|
|
1878
|
+
function shoulderLineOf(torsoTop, headY) {
|
|
1879
|
+
return (torsoTop - 3 - headY) / WALKER_HEAD_SCALE - 12;
|
|
1880
|
+
}
|
|
1844
1881
|
/** The walker's drawing for a pose, at a facing angle (0 = S, 180 = N; west facings are mirrored by the caller). */
|
|
1845
1882
|
function walkerLayout(c, pose, angleDeg, phase, outfit = outfitFor(c)) {
|
|
1846
1883
|
const v = viewOf(angleDeg);
|
|
1847
1884
|
const isSitting = pose === "sit" || pose === "play";
|
|
1848
|
-
const h = isSitting ? SEATED :
|
|
1885
|
+
const h = isSitting ? SEATED : WALKER_STANDING;
|
|
1849
1886
|
const cycle = Math.sin(phase * Math.PI * 2);
|
|
1850
|
-
const
|
|
1851
|
-
const hop = pose === "jump" || pose === "cheer" ? Math.abs(Math.sin(phase * Math.PI)) * (pose === "jump" ?
|
|
1852
|
-
const bob =
|
|
1887
|
+
const gait = isSitting ? null : gaitOf(pose);
|
|
1888
|
+
const hop = pose === "jump" || pose === "cheer" ? Math.abs(Math.sin(phase * Math.PI)) * (pose === "jump" ? 12 : 6) : 0;
|
|
1889
|
+
const bob = gait ? -gait.bounce * (1 - Math.abs(cycle)) : pose === "idle" ? cycle * .6 : 0;
|
|
1890
|
+
const rock = gait ? cycle * gait.rock * (.4 + .6 * Math.abs(v.front)) : 0;
|
|
1853
1891
|
const limbs = isSitting ? seatedLimbs(v, h, pose) : standingLimbs(v, h, pose, phase, bob);
|
|
1854
1892
|
const o = outfit;
|
|
1855
1893
|
const [farSide, nearSide] = nearness(v, 1) > nearness(v, -1) ? [-1, 1] : [1, -1];
|
|
@@ -1863,7 +1901,7 @@ function walkerLayout(c, pose, angleDeg, phase, outfit = outfitFor(c)) {
|
|
|
1863
1901
|
elbow,
|
|
1864
1902
|
wrist
|
|
1865
1903
|
], ARM_W, c.skin, c.skinInk),
|
|
1866
|
-
...limb([shoulder, add(shoulder, [elbow[0] - shoulder[0], elbow[1] - shoulder[1]], .
|
|
1904
|
+
...limb([shoulder, add(shoulder, [elbow[0] - shoulder[0], elbow[1] - shoulder[1]], .7)], SLEEVE_W, c.body, c.bodyInk),
|
|
1867
1905
|
hand(c, wrist)
|
|
1868
1906
|
];
|
|
1869
1907
|
};
|
|
@@ -1872,29 +1910,33 @@ function walkerLayout(c, pose, angleDeg, phase, outfit = outfitFor(c)) {
|
|
|
1872
1910
|
const foot = points[2];
|
|
1873
1911
|
return [...limb(points, LEG_W, o.trousers, o.trousersInk), shoe(foot, v.forward, o)];
|
|
1874
1912
|
};
|
|
1875
|
-
const
|
|
1913
|
+
const torsoTop = h.shoulder + bob;
|
|
1914
|
+
const torso = shirt(c, v.width, torsoTop, h.hip + 3 + bob, lean, v.isBack);
|
|
1876
1915
|
const isTucked = isSitting && v.isBack;
|
|
1877
1916
|
const behind = isTucked ? [...armNodes(farSide), ...armNodes(nearSide)] : isTurned ? armNodes(farSide) : [];
|
|
1878
1917
|
const isLegsFirst = v.isBack || !isSitting;
|
|
1879
1918
|
const legs = isTucked ? [] : [...legNodes(farSide), ...legNodes(nearSide)];
|
|
1880
1919
|
const body = [
|
|
1881
1920
|
...isLegsFirst ? legs : [],
|
|
1882
|
-
hips(o, v.width, h.hip +
|
|
1883
|
-
neck(c,
|
|
1921
|
+
hips(o, v.width, h.hip + bob),
|
|
1922
|
+
neck(c, torsoTop, v.forward[0] * 2),
|
|
1884
1923
|
...torso,
|
|
1885
1924
|
...isLegsFirst ? [] : legs,
|
|
1886
1925
|
...isTucked ? [] : isTurned ? armNodes(nearSide) : [...armNodes(-1), ...armNodes(1)]
|
|
1887
1926
|
];
|
|
1888
|
-
const
|
|
1927
|
+
const rocked = (nodes) => rock === 0 || nodes.length === 0 ? nodes : [g(nodes, { tf: [rot(rock)] })];
|
|
1928
|
+
const headY = h.head + bob * 1.1;
|
|
1929
|
+
const prop = add([0, h.hip - 6], v.forward, 8);
|
|
1889
1930
|
return {
|
|
1890
|
-
behind,
|
|
1891
|
-
body,
|
|
1931
|
+
behind: rocked(behind),
|
|
1932
|
+
body: rocked(body),
|
|
1892
1933
|
front: [],
|
|
1893
|
-
headDx: v.forward[0] * 1.5,
|
|
1934
|
+
headDx: v.forward[0] * 1.5 - headY * Math.sin(rock * DEG$1),
|
|
1894
1935
|
headDy: 0,
|
|
1895
|
-
headY
|
|
1936
|
+
headY,
|
|
1896
1937
|
headScale: WALKER_HEAD_SCALE,
|
|
1897
|
-
|
|
1938
|
+
shoulderLine: shoulderLineOf(torsoTop, headY),
|
|
1939
|
+
tilt: (gait ? v.side * 2 : 0) + rock,
|
|
1898
1940
|
lift: hop,
|
|
1899
1941
|
prop: {
|
|
1900
1942
|
x: prop[0],
|
|
@@ -2021,24 +2063,37 @@ function backOfHead(phi) {
|
|
|
2021
2063
|
half: { shapes: [rect(-200, -200, 200 + cx, 400, {})] }
|
|
2022
2064
|
};
|
|
2023
2065
|
}
|
|
2024
|
-
const
|
|
2025
|
-
|
|
2066
|
+
const splits = /* @__PURE__ */ new Map();
|
|
2067
|
+
/** The split at a shoulder line (head space), cached per line. */
|
|
2068
|
+
function shoulderSplit(line) {
|
|
2069
|
+
const at = Math.round(line * 4) / 4;
|
|
2070
|
+
let split = splits.get(at);
|
|
2071
|
+
if (!split) {
|
|
2072
|
+
split = {
|
|
2073
|
+
y: at,
|
|
2074
|
+
above: { shapes: [rect(-200, -200, 400, 200 + at + 1, {}), path(HEAD_PATH.d(), {})] },
|
|
2075
|
+
below: { shapes: [rect(-200, at - 1, 400, 300, {})] }
|
|
2076
|
+
};
|
|
2077
|
+
splits.set(at, split);
|
|
2078
|
+
}
|
|
2079
|
+
return split;
|
|
2080
|
+
}
|
|
2026
2081
|
/** True when a layer reaches below the shoulder line. */
|
|
2027
|
-
function hangs(shapes) {
|
|
2028
|
-
return shapes?.some((s) => s.d.bottom() >
|
|
2082
|
+
function hangs(shapes, split) {
|
|
2083
|
+
return shapes?.some((s) => s.d.bottom() > split.y) === true;
|
|
2029
2084
|
}
|
|
2030
2085
|
/** Splits a hair layer at the shoulders: the part above stays in `over`, the part below goes in `behind`. */
|
|
2031
|
-
function splitAtShoulders(nodes, doesHang, over, behind) {
|
|
2086
|
+
function splitAtShoulders(nodes, split, doesHang, over, behind) {
|
|
2032
2087
|
if (nodes.length === 0) return;
|
|
2033
2088
|
if (!doesHang) {
|
|
2034
2089
|
over.push(...nodes);
|
|
2035
2090
|
return;
|
|
2036
2091
|
}
|
|
2037
|
-
over.push(g(nodes, { clip:
|
|
2038
|
-
behind.push(g(nodes, { clip:
|
|
2092
|
+
over.push(g(nodes, { clip: split.above }));
|
|
2093
|
+
behind.push(g(nodes, { clip: split.below }));
|
|
2039
2094
|
}
|
|
2040
2095
|
/** The front and side views of the head. */
|
|
2041
|
-
function frontHead(ctx, p, isPlaying, status, angle, gazeX) {
|
|
2096
|
+
function frontHead(ctx, p, isPlaying, status, angle, gazeX, split) {
|
|
2042
2097
|
const hatCut = p.hat && typeof p.hat.hides === "number" ? { shapes: [rect(-120, p.hat.hides, 240, 260, {})] } : void 0;
|
|
2043
2098
|
const isHairShown = !(p.hat && p.hat.hides === "all");
|
|
2044
2099
|
const cut = (nodes) => hatCut && nodes.length > 0 ? [g(nodes, { clip: hatCut })] : nodes;
|
|
@@ -2058,7 +2113,7 @@ function frontHead(ctx, p, isPlaying, status, angle, gazeX) {
|
|
|
2058
2113
|
const shift = (px, py) => [px * (.92 + .08 * co) - turnS * 5, py];
|
|
2059
2114
|
const clips = backOfHead(ctx.phi);
|
|
2060
2115
|
const onFarSide = (nodes) => nodes.length > 0 ? [g([g(nodes, { clip: clips.crescent })], { clip: clips.half })] : [];
|
|
2061
|
-
if (isHairShown) splitAtShoulders(onFarSide(cut(paintShapes(ctx.c, hairTie, p.hair?.rear, shift))), hangs(p.hair?.rear), out, behindBody);
|
|
2116
|
+
if (isHairShown) splitAtShoulders(onFarSide(cut(paintShapes(ctx.c, hairTie, p.hair?.rear, shift))), split, hangs(p.hair?.rear, split), out, behindBody);
|
|
2062
2117
|
if (p.hat) out.push(...onFarSide(paintShapes(ctx.c, hatTones, p.hat.rear, shift)));
|
|
2063
2118
|
}
|
|
2064
2119
|
if (!p.hat?.hidesEars) out.push(...ears(ctx, "front", p.earExtra));
|
|
@@ -2075,7 +2130,7 @@ function frontHead(ctx, p, isPlaying, status, angle, gazeX) {
|
|
|
2075
2130
|
out.push(...nose(ctx), ...mouth(ctx));
|
|
2076
2131
|
out.push(...paintShapes(ctx.c, hairTie, p.beard?.moustache, faceWarp));
|
|
2077
2132
|
if (isHairShown) {
|
|
2078
|
-
splitAtShoulders(cut(paintShapes(ctx.c, hairTie, p.hair?.front, warpFront)), hangs(p.hair?.front), out, behindBody);
|
|
2133
|
+
splitAtShoulders(cut(paintShapes(ctx.c, hairTie, p.hair?.front, warpFront)), split, hangs(p.hair?.front, split), out, behindBody);
|
|
2079
2134
|
if (p.hair?.front?.some((s) => s.clip === "head")) out.push(path(HEAD_PATH.d(), {
|
|
2080
2135
|
fill: "none",
|
|
2081
2136
|
stroke: ctx.c.skinInk,
|
|
@@ -2156,21 +2211,23 @@ function anchorsFor(headX, headY, top, prop, mirror) {
|
|
|
2156
2211
|
pod: at(0, 14)
|
|
2157
2212
|
};
|
|
2158
2213
|
}
|
|
2159
|
-
/** The body for a figure
|
|
2214
|
+
/** The body for a figure. */
|
|
2160
2215
|
function figureLayout(c, opts, pose, phi, angle, phase) {
|
|
2161
2216
|
if (isWalker(opts)) {
|
|
2162
2217
|
const walker = walkerLayout(c, pose, angle, phase);
|
|
2163
2218
|
return {
|
|
2164
2219
|
b: walker,
|
|
2165
2220
|
headY: walker.headY,
|
|
2166
|
-
headScale: walker.headScale
|
|
2221
|
+
headScale: walker.headScale,
|
|
2222
|
+
shoulderLine: walker.shoulderLine
|
|
2167
2223
|
};
|
|
2168
2224
|
}
|
|
2169
2225
|
const b = bodyLayout(c, pose, phi, phase, opts.stool === true);
|
|
2170
2226
|
return {
|
|
2171
2227
|
b,
|
|
2172
2228
|
headY: -96 + b.headDy,
|
|
2173
|
-
headScale: 1
|
|
2229
|
+
headScale: 1,
|
|
2230
|
+
shoulderLine: 36
|
|
2174
2231
|
};
|
|
2175
2232
|
}
|
|
2176
2233
|
/** Composes a look (or `null` for the silhouette) with a pose. */
|
|
@@ -2184,7 +2241,7 @@ function composeAvatar(look, opts = {}) {
|
|
|
2184
2241
|
if (isWalker(opts)) {
|
|
2185
2242
|
const shape = walkerSilhouette();
|
|
2186
2243
|
return {
|
|
2187
|
-
nodes: [...hasShadow ? [groundShadow(0,
|
|
2244
|
+
nodes: [...hasShadow ? [groundShadow(0, 22)] : [], ...shape.nodes],
|
|
2188
2245
|
anchors: anchorsFor(0, shape.headY, shape.top, {
|
|
2189
2246
|
x: 0,
|
|
2190
2247
|
y: -40
|
|
@@ -2220,12 +2277,12 @@ function composeAvatar(look, opts = {}) {
|
|
|
2220
2277
|
blink: pose.blink ?? 0,
|
|
2221
2278
|
talk: pose.talk ?? 0
|
|
2222
2279
|
};
|
|
2223
|
-
const { b, headY: bodyHeadY, headScale } = figureLayout(parts.c, opts, bodyPose, ctx.phi, angle, pose.phase ?? .25);
|
|
2280
|
+
const { b, headY: bodyHeadY, headScale, shoulderLine } = figureLayout(parts.c, opts, bodyPose, ctx.phi, angle, pose.phase ?? .25);
|
|
2224
2281
|
const headX = b.headDx + (pose.bob?.x ?? 0);
|
|
2225
2282
|
const headY = bodyHeadY + (pose.bob?.y ?? 0);
|
|
2226
2283
|
const tilt = face.headTilt + b.tilt + (pose.tilt ?? 0);
|
|
2227
2284
|
const sq = face.squash + (pose.squash ?? 0);
|
|
2228
|
-
const drawn = isBack ? backHead(ctx, parts, isPlaying, status, angle) : frontHead(ctx, parts, isPlaying, status, angle, gaze.x);
|
|
2285
|
+
const drawn = isBack ? backHead(ctx, parts, isPlaying, status, angle) : frontHead(ctx, parts, isPlaying, status, angle, gaze.x, shoulderSplit(shoulderLine));
|
|
2229
2286
|
const headNodes = drawn.head;
|
|
2230
2287
|
const top = headY - (50 + Math.max(parts.hair?.top ?? 0, parts.hat?.top ?? 0)) * headScale;
|
|
2231
2288
|
if (status === "crown") headNodes.push(...crown(Math.max(parts.hair?.top ?? 0, parts.hat?.top ?? 0)));
|
|
@@ -2250,7 +2307,7 @@ function composeAvatar(look, opts = {}) {
|
|
|
2250
2307
|
];
|
|
2251
2308
|
const lifted = b.lift > 0 ? [g(character, { tf: [tr(0, -b.lift)] })] : character;
|
|
2252
2309
|
const rimmed = opts.rim ? [g(lifted, { rim: true })] : lifted;
|
|
2253
|
-
const all = [...hasShadow ? [groundShadow(b.lift, headScale === 1 ? 36 :
|
|
2310
|
+
const all = [...hasShadow ? [groundShadow(b.lift, headScale === 1 ? 36 : 22)] : [], ...rimmed];
|
|
2254
2311
|
const mirror = isMirrored ? -1 : 1;
|
|
2255
2312
|
return {
|
|
2256
2313
|
nodes: isMirrored ? [g(all, { tf: [sc(-1, 1)] })] : all,
|