@diffenderfer-games/hub 2.0.0-next.7 → 2.0.0-next.9
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 +43 -4
- package/dist/auto.d.ts +3 -3
- package/dist/auto.js +2 -2
- package/dist/avatar-pixi.d.ts +19 -1
- package/dist/avatar-pixi.d.ts.map +1 -1
- package/dist/avatar-pixi.js +37 -18
- package/dist/avatar-pixi.js.map +1 -1
- package/dist/avatar-svg.d.ts +1 -1
- package/dist/avatar-svg.js +1 -1
- package/dist/avatar-three.d.ts +1 -1
- package/dist/avatar.d.ts +3 -3
- package/dist/avatar.js +20 -7
- package/dist/avatar.js.map +1 -1
- package/dist/doctor/cli.mjs +58 -4
- package/dist/doctor/cli.mjs.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/shared/animator.d.ts +1 -1
- package/dist/shared/art.js +1 -1
- package/dist/shared/atlas.d.ts +5 -1
- package/dist/shared/atlas.d.ts.map +1 -1
- package/dist/shared/avatar.d.ts +10 -4
- package/dist/shared/avatar.d.ts.map +1 -1
- package/dist/shared/bridge.js +2 -2
- package/dist/shared/bridge.js.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 +1 -1
- package/dist/shared/describe.d.ts.map +1 -1
- package/dist/shared/describe.js +1 -1
- package/dist/shared/index.d.ts +88 -11
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/live-canvas.js +1 -1
- package/dist/shared/overlays.js +1 -1
- package/dist/shared/overlays.js.map +1 -1
- package/dist/shared/pose-key.js +2 -2
- package/dist/shared/pose-key.js.map +1 -1
- package/dist/shared/social.d.ts +19 -4
- package/dist/shared/social.d.ts.map +1 -1
- package/dist/shared/src.js +178 -28
- package/dist/shared/src.js.map +1 -1
- package/dist/shared/svg-markup.js +430 -21
- package/dist/shared/svg-markup.js.map +1 -1
- package/dist/shared/svg.js +2 -2
- package/dist/shared/svg.js.map +1 -1
- package/dist/shared/types.d.ts +9 -1
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/social.d.ts +2 -2
- package/dist/turns-rules.d.ts +2 -0
- package/dist/turns-rules.js +1 -0
- package/dist/turns.d.ts +23 -1
- package/dist/turns.d.ts.map +1 -1
- package/dist/turns.js +79 -13
- package/dist/turns.js.map +1 -1
- package/package.json +8 -2
- package/src/avatar/adapters/pixi-crowd.ts +45 -18
- package/src/avatar/atlas.ts +16 -6
- package/src/avatar/layout.ts +1 -1
- package/src/avatar/outputs/canvas.ts +2 -2
- package/src/avatar/outputs/svg.ts +2 -2
- package/src/avatar/render/body.ts +2 -2
- package/src/avatar/render/compose.ts +25 -8
- package/src/avatar/render/frame.ts +15 -2
- package/src/avatar/render/types.ts +9 -0
- package/src/avatar/render/walker-parts.ts +121 -0
- package/src/avatar/render/walker.ts +199 -0
- package/src/avatar.ts +2 -1
- package/src/core/rest/routes.gen.ts +1 -0
- package/src/core/turns/hub-turns.ts +25 -4
- package/src/core/turns/turn-game.ts +143 -21
- package/src/core/turns/your-turn-buzz.ts +69 -0
- 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/world-check.ts +61 -0
- package/src/index.ts +2 -1
- package/src/realtime/runtime-api.ts +5 -2
- package/src/realtime/types.ts +1 -0
- package/src/realtime/world-types.ts +20 -0
- package/src/turns-kit/check-rules.ts +116 -17
- package/src/turns-rules.ts +12 -0
- package/src/turns.ts +4 -3
|
@@ -232,6 +232,21 @@ const FRAMES = {
|
|
|
232
232
|
206
|
|
233
233
|
]
|
|
234
234
|
};
|
|
235
|
+
/** The walker's full frame: a smaller head, and room for feet hanging below a seat. */
|
|
236
|
+
const WALKER_FULL = [
|
|
237
|
+
-60,
|
|
238
|
+
-176,
|
|
239
|
+
120,
|
|
240
|
+
214
|
|
241
|
+
];
|
|
242
|
+
/** True when options draw the walker figure (full frames only). */
|
|
243
|
+
function isWalker(opts) {
|
|
244
|
+
return opts.figure === "walker" && frameOf(opts) === "full";
|
|
245
|
+
}
|
|
246
|
+
/** The box options draw, in rig units. */
|
|
247
|
+
function frameBox(opts) {
|
|
248
|
+
return isWalker(opts) ? WALKER_FULL : FRAMES[frameOf(opts)];
|
|
249
|
+
}
|
|
235
250
|
/** The site panel colour behind badges. */
|
|
236
251
|
const PANEL = "#11141c";
|
|
237
252
|
/** The frame an options object asks for (default `head`). */
|
|
@@ -240,7 +255,7 @@ function frameOf(opts) {
|
|
|
240
255
|
}
|
|
241
256
|
/** Output pixel size for a frame (the longer edge is `size`). */
|
|
242
257
|
function pixelSize(opts) {
|
|
243
|
-
const [, , w, h] =
|
|
258
|
+
const [, , w, h] = frameBox(opts);
|
|
244
259
|
const scale = (opts.size ?? 48) / Math.max(w, h);
|
|
245
260
|
return {
|
|
246
261
|
width: w * scale,
|
|
@@ -254,7 +269,7 @@ function ringColour(look) {
|
|
|
254
269
|
}
|
|
255
270
|
/** Wraps the avatar's nodes in the frame's background (a badge clips to the rounded panel). */
|
|
256
271
|
function framed(nodes, look, opts) {
|
|
257
|
-
const [left, top, w, h] =
|
|
272
|
+
const [left, top, w, h] = frameBox(opts);
|
|
258
273
|
const background = opts.background ?? (frameOf(opts) === "head" ? "badge" : "none");
|
|
259
274
|
if (background === "badge") {
|
|
260
275
|
const r = w * .24;
|
|
@@ -991,7 +1006,7 @@ const STOOL = [
|
|
|
991
1006
|
sw: 3
|
|
992
1007
|
}, 5.5)
|
|
993
1008
|
];
|
|
994
|
-
const hand = (c, cx, cy) => ellipse(cx, cy, 8, 8, {
|
|
1009
|
+
const hand$1 = (c, cx, cy) => ellipse(cx, cy, 8, 8, {
|
|
995
1010
|
fill: c.body,
|
|
996
1011
|
stroke: c.bodyInk,
|
|
997
1012
|
sw: LINE
|
|
@@ -1034,13 +1049,13 @@ function bodyLayout(c, pose, phi, phase, hasStool) {
|
|
|
1034
1049
|
tf = [rot(-6)];
|
|
1035
1050
|
headDx = -5;
|
|
1036
1051
|
tilt = -7;
|
|
1037
|
-
front.push(hand(c, 44, -58), path("M56 -70Q61 -64 58 -56M62 -76Q69 -66 64 -54", line("#e8e6df", 2.2, "round", .8)));
|
|
1052
|
+
front.push(hand$1(c, 44, -58), path("M56 -70Q61 -64 58 -56M62 -76Q69 -66 64 -54", line("#e8e6df", 2.2, "round", .8)));
|
|
1038
1053
|
break;
|
|
1039
1054
|
case "cheer":
|
|
1040
1055
|
lift = hop * 10;
|
|
1041
1056
|
tf = [sc(1 - hop * .04, 1 + hop * .05)];
|
|
1042
1057
|
headDy = -4 * hop;
|
|
1043
|
-
front.push(hand(c, -42, -66 - hop * 4), hand(c, 42, -66 - hop * 4));
|
|
1058
|
+
front.push(hand$1(c, -42, -66 - hop * 4), hand$1(c, 42, -66 - hop * 4));
|
|
1044
1059
|
break;
|
|
1045
1060
|
case "jump":
|
|
1046
1061
|
lift = hop * 18;
|
|
@@ -1085,9 +1100,9 @@ function bodyLayout(c, pose, phi, phase, hasStool) {
|
|
|
1085
1100
|
};
|
|
1086
1101
|
}
|
|
1087
1102
|
/** The soft ground shadow at the feet (shrinks while lifted). */
|
|
1088
|
-
function groundShadow(lift) {
|
|
1103
|
+
function groundShadow(lift, width = 36) {
|
|
1089
1104
|
const k = Math.max(.5, 1 - lift / 40);
|
|
1090
|
-
return ellipse(0, 0,
|
|
1105
|
+
return ellipse(0, 0, width * k, width / 4.5 * k, {
|
|
1091
1106
|
fill: "#000000",
|
|
1092
1107
|
opacity: .3
|
|
1093
1108
|
});
|
|
@@ -1573,6 +1588,366 @@ function handProp(prop, atX, atY, body, bodyInk) {
|
|
|
1573
1588
|
}
|
|
1574
1589
|
}
|
|
1575
1590
|
//#endregion
|
|
1591
|
+
//#region src/avatar/render/walker-parts.ts
|
|
1592
|
+
/**
|
|
1593
|
+
* The walker's drawing parts (`figure: 'walker'`): limbs as outlined round
|
|
1594
|
+
* strokes, the shirt, trousers and shoes. Colours come from the look: the
|
|
1595
|
+
* shirt is the body colour, the trousers and shoes are picked from the body
|
|
1596
|
+
* colour so they always stand apart from the shirt.
|
|
1597
|
+
*/
|
|
1598
|
+
const TROUSERS = [
|
|
1599
|
+
"#34466e",
|
|
1600
|
+
"#2e323f",
|
|
1601
|
+
"#6a5642",
|
|
1602
|
+
"#4a3a60",
|
|
1603
|
+
"#3c5a4c",
|
|
1604
|
+
"#5a3a3a"
|
|
1605
|
+
];
|
|
1606
|
+
const SHOES = [
|
|
1607
|
+
"#2c2430",
|
|
1608
|
+
"#f1ece2",
|
|
1609
|
+
"#5a3a2a",
|
|
1610
|
+
"#2c2430"
|
|
1611
|
+
];
|
|
1612
|
+
/** A small stable hash of a colour string. */
|
|
1613
|
+
function hashOf(text) {
|
|
1614
|
+
let hash = 7;
|
|
1615
|
+
for (let i = 0; i < text.length; i++) hash = hash * 31 + text.charCodeAt(i) >>> 0;
|
|
1616
|
+
return hash;
|
|
1617
|
+
}
|
|
1618
|
+
/** Trousers and shoes for a shirt colour: a trouser tone at least a little lighter or darker than the shirt. */
|
|
1619
|
+
function outfitFor(c) {
|
|
1620
|
+
const hash = hashOf(c.body);
|
|
1621
|
+
const shirt = lightness(c.body);
|
|
1622
|
+
let trousers = TROUSERS[hash % TROUSERS.length] ?? TROUSERS[0];
|
|
1623
|
+
for (let i = 1; i < TROUSERS.length && Math.abs(lightness(trousers) - shirt) < .12; i++) trousers = TROUSERS[(hash + i) % TROUSERS.length] ?? trousers;
|
|
1624
|
+
const shoes = SHOES[(hash >>> 4) % SHOES.length] ?? SHOES[0];
|
|
1625
|
+
return {
|
|
1626
|
+
trousers,
|
|
1627
|
+
trousersInk: outline(trousers),
|
|
1628
|
+
shoes,
|
|
1629
|
+
shoesInk: outline(shoes)
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
/** A polyline as a path `d`. */
|
|
1633
|
+
function polyline(points) {
|
|
1634
|
+
const line = new Path();
|
|
1635
|
+
points.forEach(([px, py], i) => i === 0 ? line.m(px, py) : line.l(px, py));
|
|
1636
|
+
return line.d();
|
|
1637
|
+
}
|
|
1638
|
+
/** A limb: a round stroke with a tonal outline under it. */
|
|
1639
|
+
function limb(points, width, fill, ink) {
|
|
1640
|
+
const d = polyline(points);
|
|
1641
|
+
return [path(d, {
|
|
1642
|
+
fill: "none",
|
|
1643
|
+
stroke: ink,
|
|
1644
|
+
sw: width + LINE * 1.3,
|
|
1645
|
+
cap: "round"
|
|
1646
|
+
}), path(d, {
|
|
1647
|
+
fill: "none",
|
|
1648
|
+
stroke: fill,
|
|
1649
|
+
sw: width,
|
|
1650
|
+
cap: "round"
|
|
1651
|
+
})];
|
|
1652
|
+
}
|
|
1653
|
+
/** A shoe at a foot point, nudged along the facing. */
|
|
1654
|
+
function shoe(at, forward, o) {
|
|
1655
|
+
return ellipse(at[0] + forward[0] * 2.4, at[1] + forward[1] * 1.6 - 1.5, 6.2 + Math.abs(forward[0]) * 1.4, 3.8, {
|
|
1656
|
+
fill: o.shoes,
|
|
1657
|
+
stroke: o.shoesInk,
|
|
1658
|
+
sw: LINE * .8
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1661
|
+
/** A hand. */
|
|
1662
|
+
function hand(c, at) {
|
|
1663
|
+
return ellipse(at[0], at[1], 4.3, 4.3, {
|
|
1664
|
+
fill: c.skin,
|
|
1665
|
+
stroke: c.skinInk,
|
|
1666
|
+
sw: LINE * .8
|
|
1667
|
+
});
|
|
1668
|
+
}
|
|
1669
|
+
/** The torso's outline: shoulders at `top`, the hem at `hem`, `w` its width factor (narrower when turned). */
|
|
1670
|
+
function torsoPath(w, top, hem) {
|
|
1671
|
+
const shoulder = 17.5 * w;
|
|
1672
|
+
const waist = 14.5 * w;
|
|
1673
|
+
const hip = 15.5 * w;
|
|
1674
|
+
const mid = (top + hem) / 2 + 4;
|
|
1675
|
+
return new Path().m(-shoulder, top + 6).c(-shoulder, top - 1, -shoulder + 5, top - 3, -shoulder * .35, top - 3).l(shoulder * .35, top - 3).c(shoulder - 5, top - 3, shoulder, top - 1, shoulder, top + 6).l(waist, mid).l(hip, hem).q(0, hem + 3, -hip, hem).l(-waist, mid).z().d();
|
|
1676
|
+
}
|
|
1677
|
+
/** The shirt: a shade base, the lit front, the outline, and the neckline (front) or collar (back). */
|
|
1678
|
+
function shirt(c, w, top, hem, lean, isBack) {
|
|
1679
|
+
const d = torsoPath(w, top, hem);
|
|
1680
|
+
const cy = (top + hem) / 2;
|
|
1681
|
+
const nodes = [
|
|
1682
|
+
path(d, { fill: c.bodyShade }),
|
|
1683
|
+
g([path(d, { fill: c.body })], { tf: [
|
|
1684
|
+
tr(-1.5 - lean * 2, cy - 1.5),
|
|
1685
|
+
sc(.9, .94),
|
|
1686
|
+
tr(0, -cy)
|
|
1687
|
+
] }),
|
|
1688
|
+
path(`M${String(-15 * w)} ${String(hem - 2.5)}Q0 ${String(hem + .5)} ${String(15 * w)} ${String(hem - 2.5)}`, {
|
|
1689
|
+
fill: "none",
|
|
1690
|
+
stroke: shade(c.bodyShade),
|
|
1691
|
+
sw: 2.2,
|
|
1692
|
+
opacity: .7
|
|
1693
|
+
}),
|
|
1694
|
+
path(d, {
|
|
1695
|
+
fill: "none",
|
|
1696
|
+
stroke: c.bodyInk,
|
|
1697
|
+
sw: LINE
|
|
1698
|
+
})
|
|
1699
|
+
];
|
|
1700
|
+
const neckX = lean * 7;
|
|
1701
|
+
if (isBack) nodes.push(path(`M${String(neckX - 6)} ${String(top - 2)}Q${String(neckX)} ${String(top + 1.5)} ${String(neckX + 6)} ${String(top - 2)}`, {
|
|
1702
|
+
fill: "none",
|
|
1703
|
+
stroke: c.bodyShade,
|
|
1704
|
+
sw: 3
|
|
1705
|
+
}));
|
|
1706
|
+
else nodes.push(path(`M${String(neckX - 6 * w)} ${String(top - 2.6)}Q${String(neckX)} ${String(top + 6)} ${String(neckX + 6 * w)} ${String(top - 2.6)}Z`, {
|
|
1707
|
+
fill: c.skin,
|
|
1708
|
+
stroke: c.bodyInk,
|
|
1709
|
+
sw: LINE * .8
|
|
1710
|
+
}));
|
|
1711
|
+
return nodes;
|
|
1712
|
+
}
|
|
1713
|
+
/** The seat of the trousers joining the legs under the hem. */
|
|
1714
|
+
function hips(o, w, hipY) {
|
|
1715
|
+
const half = 12 * w;
|
|
1716
|
+
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
|
+
fill: o.trousers,
|
|
1718
|
+
stroke: o.trousersInk,
|
|
1719
|
+
sw: LINE * .8
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
/** The neck under the head. */
|
|
1723
|
+
function neck(c, top, dx) {
|
|
1724
|
+
return path(new Path().m(dx - 4.5, top - 10).l(dx + 4.5, top - 10).l(dx + 4.5, top).l(dx - 4.5, top).z().d(), {
|
|
1725
|
+
fill: c.skinShade,
|
|
1726
|
+
stroke: c.skinInk,
|
|
1727
|
+
sw: LINE * .6
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
//#endregion
|
|
1731
|
+
//#region src/avatar/render/walker.ts
|
|
1732
|
+
/**
|
|
1733
|
+
* The world walker (`figure: 'walker'`): a small full-body character for
|
|
1734
|
+
* maps and crowds, with legs, arms, a shirt, trousers and shoes, and the
|
|
1735
|
+
* head drawn smaller on top ({@link WALKER_HEAD_SCALE}). It takes the blob's
|
|
1736
|
+
* pose names: `walk` and `run` swing the legs and arms along the facing,
|
|
1737
|
+
* `sit` folds the legs over a seat, `idle` breathes, `wave` and `cheer` lift
|
|
1738
|
+
* the arms.
|
|
1739
|
+
*
|
|
1740
|
+
* Standing, the origin is the feet. Sitting (`sit`, `play`), the origin is
|
|
1741
|
+
* the seat: the hips rest on it and the feet hang {@link WALKER_SEAT_DROP}
|
|
1742
|
+
* units below, on the floor in front of it.
|
|
1743
|
+
*/
|
|
1744
|
+
/** The head's scale on a walker (the blob draws it at 1). */
|
|
1745
|
+
const WALKER_HEAD_SCALE = .58;
|
|
1746
|
+
/** How far below the seat a sitter's feet reach (rig units). */
|
|
1747
|
+
const WALKER_SEAT_DROP = 26;
|
|
1748
|
+
const STANDING = {
|
|
1749
|
+
hip: -35,
|
|
1750
|
+
shoulder: -72,
|
|
1751
|
+
head: -103
|
|
1752
|
+
};
|
|
1753
|
+
const SEATED = {
|
|
1754
|
+
hip: -3,
|
|
1755
|
+
shoulder: -40,
|
|
1756
|
+
head: -71
|
|
1757
|
+
};
|
|
1758
|
+
const LEG = 32;
|
|
1759
|
+
const UPPER_ARM = 13;
|
|
1760
|
+
const FOREARM = 12;
|
|
1761
|
+
const LEG_W = 10.5;
|
|
1762
|
+
const SLEEVE_W = 10;
|
|
1763
|
+
const ARM_W = 7.5;
|
|
1764
|
+
const DEG$1 = Math.PI / 180;
|
|
1765
|
+
function viewOf(angleDeg) {
|
|
1766
|
+
const side = Math.sin(angleDeg * DEG$1);
|
|
1767
|
+
const front = Math.cos(angleDeg * DEG$1);
|
|
1768
|
+
return {
|
|
1769
|
+
side,
|
|
1770
|
+
front,
|
|
1771
|
+
forward: [side, front * .45],
|
|
1772
|
+
isBack: angleDeg > 90,
|
|
1773
|
+
width: 1 - .36 * Math.abs(side)
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
/** A body side's screen x for a lateral distance (sides overlap when turned). */
|
|
1777
|
+
const lateralX = (v, sideOf, distance) => sideOf * distance * v.front;
|
|
1778
|
+
/** How near the viewer a side is (> 0: drawn in front when turned). */
|
|
1779
|
+
const nearness = (v, sideOf) => -sideOf * v.side;
|
|
1780
|
+
const add = (a, b, k = 1) => [a[0] + b[0] * k, a[1] + b[1] * k];
|
|
1781
|
+
function standingLimbs(v, h, pose, phase, bob) {
|
|
1782
|
+
const isRun = pose === "run";
|
|
1783
|
+
const isWalking = pose === "walk" || isRun || pose === "dance";
|
|
1784
|
+
const stride = isRun ? 38 : 27;
|
|
1785
|
+
const swingOf = (sideOf) => isWalking ? Math.sin((phase + (sideOf === 1 ? 0 : .5)) * Math.PI * 2) : 0;
|
|
1786
|
+
return {
|
|
1787
|
+
leg: (sideOf) => {
|
|
1788
|
+
const hip = [lateralX(v, sideOf, 6), h.hip + bob];
|
|
1789
|
+
const swing = swingOf(sideOf);
|
|
1790
|
+
const lift = isWalking ? Math.max(0, Math.cos((phase + (sideOf === 1 ? 0 : .5)) * Math.PI * 2)) * (isRun ? 7 : 4.5) : 0;
|
|
1791
|
+
const reach = Math.sin(stride * DEG$1) * LEG * swing;
|
|
1792
|
+
const foot = [hip[0] + v.forward[0] * reach, -bob * .2 + v.forward[1] * reach - lift];
|
|
1793
|
+
return [
|
|
1794
|
+
hip,
|
|
1795
|
+
[(hip[0] + foot[0]) / 2 + v.forward[0] * lift * .9, (hip[1] + foot[1]) / 2 + v.forward[1] * lift * .9],
|
|
1796
|
+
foot
|
|
1797
|
+
];
|
|
1798
|
+
},
|
|
1799
|
+
arm: (sideOf) => {
|
|
1800
|
+
const shoulder = [lateralX(v, sideOf, 15.5 * v.width) + v.forward[0] * 1.5, h.shoulder + 4 + bob];
|
|
1801
|
+
const out = sideOf * v.front * 2.2;
|
|
1802
|
+
if (pose === "cheer" || pose === "wave" && sideOf === -1) {
|
|
1803
|
+
const wave = pose === "wave" ? Math.sin(phase * Math.PI * 2) * 4 : 0;
|
|
1804
|
+
const elbow = [shoulder[0] + out * 3, shoulder[1] - 10];
|
|
1805
|
+
return [
|
|
1806
|
+
shoulder,
|
|
1807
|
+
elbow,
|
|
1808
|
+
[elbow[0] + out * 1.5 + wave, elbow[1] - 12]
|
|
1809
|
+
];
|
|
1810
|
+
}
|
|
1811
|
+
const swing = isWalking ? -swingOf(sideOf) * (isRun ? 1.3 : 1) : Math.sin(phase * Math.PI * 2) * .06;
|
|
1812
|
+
const reach = Math.sin(24 * DEG$1) * swing;
|
|
1813
|
+
const elbow = [shoulder[0] + out + v.forward[0] * UPPER_ARM * reach * .6, shoulder[1] + UPPER_ARM + v.forward[1] * UPPER_ARM * reach];
|
|
1814
|
+
return [
|
|
1815
|
+
shoulder,
|
|
1816
|
+
elbow,
|
|
1817
|
+
[elbow[0] + out * .4 + v.forward[0] * FOREARM * reach * 1.6, elbow[1] + FOREARM - Math.abs(reach) * 3 + v.forward[1] * FOREARM * reach]
|
|
1818
|
+
];
|
|
1819
|
+
}
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
function seatedLimbs(v, h, pose) {
|
|
1823
|
+
const thigh = [v.forward[0] * 15, v.forward[1] * 16];
|
|
1824
|
+
return {
|
|
1825
|
+
leg: (sideOf) => {
|
|
1826
|
+
const hip = [lateralX(v, sideOf, 6), h.hip];
|
|
1827
|
+
const knee = add(hip, thigh);
|
|
1828
|
+
return [
|
|
1829
|
+
hip,
|
|
1830
|
+
knee,
|
|
1831
|
+
[knee[0] + v.forward[0] * 3, 24]
|
|
1832
|
+
];
|
|
1833
|
+
},
|
|
1834
|
+
arm: (sideOf) => {
|
|
1835
|
+
const shoulder = [lateralX(v, sideOf, 15.5 * v.width) + v.forward[0] * 1.5, h.shoulder + 4];
|
|
1836
|
+
return [
|
|
1837
|
+
shoulder,
|
|
1838
|
+
[shoulder[0] + sideOf * v.front * 2 + v.forward[0] * 3, shoulder[1] + UPPER_ARM],
|
|
1839
|
+
add([lateralX(v, sideOf, pose === "play" ? 4 : 7), h.hip - 4], thigh, pose === "play" ? .9 : .6)
|
|
1840
|
+
];
|
|
1841
|
+
}
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
/** The walker's drawing for a pose, at a facing angle (0 = S, 180 = N; west facings are mirrored by the caller). */
|
|
1845
|
+
function walkerLayout(c, pose, angleDeg, phase, outfit = outfitFor(c)) {
|
|
1846
|
+
const v = viewOf(angleDeg);
|
|
1847
|
+
const isSitting = pose === "sit" || pose === "play";
|
|
1848
|
+
const h = isSitting ? SEATED : STANDING;
|
|
1849
|
+
const cycle = Math.sin(phase * Math.PI * 2);
|
|
1850
|
+
const isWalking = pose === "walk" || pose === "run" || pose === "dance";
|
|
1851
|
+
const hop = pose === "jump" || pose === "cheer" ? Math.abs(Math.sin(phase * Math.PI)) * (pose === "jump" ? 16 : 8) : 0;
|
|
1852
|
+
const bob = isWalking ? -2.2 * (1 - Math.abs(cycle)) : pose === "idle" ? cycle * .6 : 0;
|
|
1853
|
+
const limbs = isSitting ? seatedLimbs(v, h, pose) : standingLimbs(v, h, pose, phase, bob);
|
|
1854
|
+
const o = outfit;
|
|
1855
|
+
const [farSide, nearSide] = nearness(v, 1) > nearness(v, -1) ? [-1, 1] : [1, -1];
|
|
1856
|
+
const isTurned = Math.abs(v.side) > .3;
|
|
1857
|
+
const lean = v.side * (isSitting ? .6 : 1);
|
|
1858
|
+
const armNodes = (sideOf) => {
|
|
1859
|
+
const [shoulder, elbow, wrist] = limbs.arm(sideOf);
|
|
1860
|
+
return [
|
|
1861
|
+
...limb([
|
|
1862
|
+
shoulder,
|
|
1863
|
+
elbow,
|
|
1864
|
+
wrist
|
|
1865
|
+
], ARM_W, c.skin, c.skinInk),
|
|
1866
|
+
...limb([shoulder, add(shoulder, [elbow[0] - shoulder[0], elbow[1] - shoulder[1]], .62)], SLEEVE_W, c.body, c.bodyInk),
|
|
1867
|
+
hand(c, wrist)
|
|
1868
|
+
];
|
|
1869
|
+
};
|
|
1870
|
+
const legNodes = (sideOf) => {
|
|
1871
|
+
const points = limbs.leg(sideOf);
|
|
1872
|
+
const foot = points[2];
|
|
1873
|
+
return [...limb(points, LEG_W, o.trousers, o.trousersInk), shoe(foot, v.forward, o)];
|
|
1874
|
+
};
|
|
1875
|
+
const torso = shirt(c, v.width, h.shoulder + (isWalking ? bob : 0), h.hip + 3 + (isWalking ? bob : 0), lean, v.isBack);
|
|
1876
|
+
const isTucked = isSitting && v.isBack;
|
|
1877
|
+
const behind = isTucked ? [...armNodes(farSide), ...armNodes(nearSide)] : isTurned ? armNodes(farSide) : [];
|
|
1878
|
+
const isLegsFirst = v.isBack || !isSitting;
|
|
1879
|
+
const legs = isTucked ? [] : [...legNodes(farSide), ...legNodes(nearSide)];
|
|
1880
|
+
const body = [
|
|
1881
|
+
...isLegsFirst ? legs : [],
|
|
1882
|
+
hips(o, v.width, h.hip + (isWalking ? bob : 0)),
|
|
1883
|
+
neck(c, h.shoulder + bob, v.forward[0] * 2),
|
|
1884
|
+
...torso,
|
|
1885
|
+
...isLegsFirst ? [] : legs,
|
|
1886
|
+
...isTucked ? [] : isTurned ? armNodes(nearSide) : [...armNodes(-1), ...armNodes(1)]
|
|
1887
|
+
];
|
|
1888
|
+
const prop = add([0, h.hip - 12], v.forward, 8);
|
|
1889
|
+
return {
|
|
1890
|
+
behind,
|
|
1891
|
+
body,
|
|
1892
|
+
front: [],
|
|
1893
|
+
headDx: v.forward[0] * 1.5,
|
|
1894
|
+
headDy: 0,
|
|
1895
|
+
headY: h.head + bob * 1.1,
|
|
1896
|
+
headScale: WALKER_HEAD_SCALE,
|
|
1897
|
+
tilt: isWalking ? v.side * 2 : 0,
|
|
1898
|
+
lift: hop,
|
|
1899
|
+
prop: {
|
|
1900
|
+
x: prop[0],
|
|
1901
|
+
y: prop[1]
|
|
1902
|
+
}
|
|
1903
|
+
};
|
|
1904
|
+
}
|
|
1905
|
+
const GREY = "#6f7787";
|
|
1906
|
+
const GREY_INK = "#5c6474";
|
|
1907
|
+
/** Every colour slot in the silhouette grey. */
|
|
1908
|
+
const GREY_COLOURS = {
|
|
1909
|
+
skin: GREY,
|
|
1910
|
+
skinShade: GREY,
|
|
1911
|
+
skinLight: GREY,
|
|
1912
|
+
skinInk: GREY_INK,
|
|
1913
|
+
eyeRim: GREY,
|
|
1914
|
+
hair: GREY,
|
|
1915
|
+
hairShade: GREY,
|
|
1916
|
+
hairLight: GREY,
|
|
1917
|
+
hairInk: GREY_INK,
|
|
1918
|
+
hairBuzz: GREY,
|
|
1919
|
+
brow: GREY,
|
|
1920
|
+
beard: GREY,
|
|
1921
|
+
beardShade: GREY,
|
|
1922
|
+
beardInk: GREY_INK,
|
|
1923
|
+
stubble: GREY,
|
|
1924
|
+
eye: GREY,
|
|
1925
|
+
eyeDark: GREY,
|
|
1926
|
+
body: GREY,
|
|
1927
|
+
bodyShade: GREY,
|
|
1928
|
+
bodyLight: GREY,
|
|
1929
|
+
bodyInk: GREY_INK
|
|
1930
|
+
};
|
|
1931
|
+
/** The grey walker shown before the art loads (or for a hidden player): its nodes, head centre and top. */
|
|
1932
|
+
function walkerSilhouette() {
|
|
1933
|
+
const b = walkerLayout(GREY_COLOURS, "idle", 0, 0, {
|
|
1934
|
+
trousers: GREY,
|
|
1935
|
+
trousersInk: GREY_INK,
|
|
1936
|
+
shoes: GREY,
|
|
1937
|
+
shoesInk: GREY_INK
|
|
1938
|
+
});
|
|
1939
|
+
const r = 46 * WALKER_HEAD_SCALE;
|
|
1940
|
+
return {
|
|
1941
|
+
nodes: [
|
|
1942
|
+
...b.behind,
|
|
1943
|
+
...b.body,
|
|
1944
|
+
ellipse(0, b.headY, r, r * 1.05, { fill: GREY })
|
|
1945
|
+
],
|
|
1946
|
+
headY: b.headY,
|
|
1947
|
+
top: b.headY - r - 4
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
//#endregion
|
|
1576
1951
|
//#region src/avatar/render/compose.ts
|
|
1577
1952
|
/**
|
|
1578
1953
|
* Composes a look and a pose into a scene in rig space: the feet at (0, 0),
|
|
@@ -1781,6 +2156,23 @@ function anchorsFor(headX, headY, top, prop, mirror) {
|
|
|
1781
2156
|
pod: at(0, 14)
|
|
1782
2157
|
};
|
|
1783
2158
|
}
|
|
2159
|
+
/** The body for a figure: its layout, where the head centre goes, and the head's scale. */
|
|
2160
|
+
function figureLayout(c, opts, pose, phi, angle, phase) {
|
|
2161
|
+
if (isWalker(opts)) {
|
|
2162
|
+
const walker = walkerLayout(c, pose, angle, phase);
|
|
2163
|
+
return {
|
|
2164
|
+
b: walker,
|
|
2165
|
+
headY: walker.headY,
|
|
2166
|
+
headScale: walker.headScale
|
|
2167
|
+
};
|
|
2168
|
+
}
|
|
2169
|
+
const b = bodyLayout(c, pose, phi, phase, opts.stool === true);
|
|
2170
|
+
return {
|
|
2171
|
+
b,
|
|
2172
|
+
headY: -96 + b.headDy,
|
|
2173
|
+
headScale: 1
|
|
2174
|
+
};
|
|
2175
|
+
}
|
|
1784
2176
|
/** Composes a look (or `null` for the silhouette) with a pose. */
|
|
1785
2177
|
function composeAvatar(look, opts = {}) {
|
|
1786
2178
|
const art = currentArt();
|
|
@@ -1788,13 +2180,25 @@ function composeAvatar(look, opts = {}) {
|
|
|
1788
2180
|
const isMirrored = MIRROR[facingIn] !== void 0;
|
|
1789
2181
|
const facing = MIRROR[facingIn] ?? (facingIn === "S" || facingIn === "SE" || facingIn === "E" || facingIn === "NE" ? facingIn : "N");
|
|
1790
2182
|
const hasShadow = opts.shadow ?? opts.frame === "full";
|
|
1791
|
-
if (!look || !art)
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
2183
|
+
if (!look || !art) {
|
|
2184
|
+
if (isWalker(opts)) {
|
|
2185
|
+
const shape = walkerSilhouette();
|
|
2186
|
+
return {
|
|
2187
|
+
nodes: [...hasShadow ? [groundShadow(0, 18)] : [], ...shape.nodes],
|
|
2188
|
+
anchors: anchorsFor(0, shape.headY, shape.top, {
|
|
2189
|
+
x: 0,
|
|
2190
|
+
y: -40
|
|
2191
|
+
}, 1)
|
|
2192
|
+
};
|
|
2193
|
+
}
|
|
2194
|
+
return {
|
|
2195
|
+
nodes: silhouette(hasShadow),
|
|
2196
|
+
anchors: anchorsFor(0, -96, -156, {
|
|
2197
|
+
x: 0,
|
|
2198
|
+
y: -30
|
|
2199
|
+
}, 1)
|
|
2200
|
+
};
|
|
2201
|
+
}
|
|
1798
2202
|
const pose = opts.pose ?? {};
|
|
1799
2203
|
const bodyPose = pose.body ?? "idle";
|
|
1800
2204
|
const status = pose.status ?? (bodyPose === "play" ? "goggles" : null);
|
|
@@ -1816,20 +2220,25 @@ function composeAvatar(look, opts = {}) {
|
|
|
1816
2220
|
blink: pose.blink ?? 0,
|
|
1817
2221
|
talk: pose.talk ?? 0
|
|
1818
2222
|
};
|
|
1819
|
-
const b =
|
|
2223
|
+
const { b, headY: bodyHeadY, headScale } = figureLayout(parts.c, opts, bodyPose, ctx.phi, angle, pose.phase ?? .25);
|
|
1820
2224
|
const headX = b.headDx + (pose.bob?.x ?? 0);
|
|
1821
|
-
const headY =
|
|
2225
|
+
const headY = bodyHeadY + (pose.bob?.y ?? 0);
|
|
1822
2226
|
const tilt = face.headTilt + b.tilt + (pose.tilt ?? 0);
|
|
1823
2227
|
const sq = face.squash + (pose.squash ?? 0);
|
|
1824
2228
|
const drawn = isBack ? backHead(ctx, parts, isPlaying, status, angle) : frontHead(ctx, parts, isPlaying, status, angle, gaze.x);
|
|
1825
2229
|
const headNodes = drawn.head;
|
|
1826
|
-
const top = headY - 50
|
|
2230
|
+
const top = headY - (50 + Math.max(parts.hair?.top ?? 0, parts.hat?.top ?? 0)) * headScale;
|
|
1827
2231
|
if (status === "crown") headNodes.push(...crown(Math.max(parts.hair?.top ?? 0, parts.hat?.top ?? 0)));
|
|
1828
|
-
const
|
|
2232
|
+
const props = status && !isBack ? handProp(status, b.prop.x, b.prop.y, parts.c.body, parts.c.bodyInk) : [];
|
|
2233
|
+
const handProps = headScale === 1 || props.length === 0 ? props : [g(props, { tf: [
|
|
2234
|
+
tr(b.prop.x, b.prop.y),
|
|
2235
|
+
sc(headScale * 1.1),
|
|
2236
|
+
tr(-b.prop.x, -b.prop.y)
|
|
2237
|
+
] })];
|
|
1829
2238
|
const headTf = [
|
|
1830
2239
|
tr(headX, headY),
|
|
1831
2240
|
rot(tilt),
|
|
1832
|
-
sc(1 + sq, 1 - sq)
|
|
2241
|
+
sc((1 + sq) * headScale, (1 - sq) * headScale)
|
|
1833
2242
|
];
|
|
1834
2243
|
const character = [
|
|
1835
2244
|
...b.behind,
|
|
@@ -1841,7 +2250,7 @@ function composeAvatar(look, opts = {}) {
|
|
|
1841
2250
|
];
|
|
1842
2251
|
const lifted = b.lift > 0 ? [g(character, { tf: [tr(0, -b.lift)] })] : character;
|
|
1843
2252
|
const rimmed = opts.rim ? [g(lifted, { rim: true })] : lifted;
|
|
1844
|
-
const all = [...hasShadow ? [groundShadow(b.lift)] : [], ...rimmed];
|
|
2253
|
+
const all = [...hasShadow ? [groundShadow(b.lift, headScale === 1 ? 36 : 18)] : [], ...rimmed];
|
|
1845
2254
|
const mirror = isMirrored ? -1 : 1;
|
|
1846
2255
|
return {
|
|
1847
2256
|
nodes: isMirrored ? [g(all, { tf: [sc(-1, 1)] })] : all,
|
|
@@ -1940,6 +2349,6 @@ function sceneToSvg(nodes, prefix) {
|
|
|
1940
2349
|
};
|
|
1941
2350
|
}
|
|
1942
2351
|
//#endregion
|
|
1943
|
-
export {
|
|
2352
|
+
export { rect as A, frameBox as C, g as D, ellipse as E, lightness as F, toCode as I, toLook as L, tr as M, contrast as N, line as O, deltaE as P, WALKER_FULL as S, pixelSize as T, faceOf as _, composeAvatar as a, onArtReady as b, Path as c, n as d, roundedRect as f, expressionFromEmote as g, EXPRESSION_NAMES as h, HEAD_Y as i, rot as j, path as k, arc as l, EXPRESSIONS as m, escapeXml as n, WALKER_HEAD_SCALE as o, scallop as p, sceneToSvg as r, WALKER_SEAT_DROP as s, RIM_COLOUR as t, blob as u, isExpression as v, framed as w, ready as x, mix as y };
|
|
1944
2353
|
|
|
1945
2354
|
//# sourceMappingURL=svg-markup.js.map
|