@fabi-play/engine 0.3.0 → 0.4.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/dist/fabi-play-engine.d.ts +14 -8
- package/dist/fabi-play-engine.js +13 -13
- package/dist/fabi-play-engine.lite.d.ts +14 -8
- package/dist/fabi-play-engine.lite.js +5 -5
- package/dist/fabi-play-engine.lite.umd.js +5 -1
- package/dist/fabi-play-engine.lite.umd.min.js +2 -2
- package/dist/fabi-play-engine.umd.js +260 -22
- package/dist/fabi-play-engine.umd.min.js +13 -13
- package/dist/index.d.ts +14 -8
- package/dist/index.development.js +4 -0
- package/dist/index.js +5 -5
- package/dist/integrity.json +9 -9
- package/package.json +5 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @fabi-play/engine 0.
|
|
1
|
+
/*! @fabi-play/engine 0.4.0 (full) | Fabi Play Kit License | https://docs.fabiplay.app */
|
|
2
2
|
(function (root, factory) {
|
|
3
3
|
var script = typeof document !== 'undefined' ? document.currentScript : null;
|
|
4
4
|
if (typeof define === 'function' && define.amd) define([], function () { return factory(script); });
|
|
@@ -250,6 +250,7 @@ var AVATARS = [
|
|
|
250
250
|
id: "lion",
|
|
251
251
|
name: { en: "Lion", it: "Leoncino" },
|
|
252
252
|
band: "6-8",
|
|
253
|
+
alsoIn: ["3-5"],
|
|
253
254
|
palette: { body: "#f6c454", limbs: "#f6c454", belly: "#fde7b0", accent: "#fde7b0", outline: INK },
|
|
254
255
|
head: "round",
|
|
255
256
|
ears: "round",
|
|
@@ -616,6 +617,39 @@ var AVATARS = [
|
|
|
616
617
|
headExtras: ["hair", "domino"],
|
|
617
618
|
bodyExtras: ["cape", "emblem", "boots"],
|
|
618
619
|
extraColors: ["#ffd84a"]
|
|
620
|
+
},
|
|
621
|
+
// ── In both 3–5 and 6–8 (home 6–8, alsoIn 3–5: drawn with the proportions of the band shown) ──
|
|
622
|
+
{
|
|
623
|
+
// Next to the round 'dinosaur': a T-rex at a glance, a big head turned three-quarters with a long snout and a
|
|
624
|
+
// toothy grin, tiny arms, strong legs, a thick striped tail.
|
|
625
|
+
id: "trex",
|
|
626
|
+
name: { en: "T-Rex", it: "T-Rex" },
|
|
627
|
+
band: "6-8",
|
|
628
|
+
alsoIn: ["3-5"],
|
|
629
|
+
palette: { body: "#6cb84e", limbs: "#6cb84e", belly: "#f4e8a8", accent: "#f4e8a8", outline: INK },
|
|
630
|
+
head: "snout-long",
|
|
631
|
+
ears: "none",
|
|
632
|
+
face: { eyes: "dot", mouth: "toothy", muzzle: "none", cheeks: "#f59b8f" },
|
|
633
|
+
tail: "thick",
|
|
634
|
+
headExtras: ["stripes"],
|
|
635
|
+
bodyExtras: ["tiny-arms", "strong-legs"],
|
|
636
|
+
extraColors: ["#4a9235", "#58a23f"]
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
// An original child fairy: a bun with a flower crown, little pointed ears, a petal tunic, see-through wings and
|
|
640
|
+
// a star wand in one hand.
|
|
641
|
+
id: "fairy",
|
|
642
|
+
name: { en: "Fairy", it: "Fatina" },
|
|
643
|
+
band: "6-8",
|
|
644
|
+
alsoIn: ["3-5"],
|
|
645
|
+
palette: { body: "#b99af0", limbs: "#a8693f", belly: "#b99af0", accent: "#ffd84a", outline: INK, skin: "#a8693f", hair: "#2e1d17", hands: "#a8693f", feet: "#f58cbc" },
|
|
646
|
+
head: "round",
|
|
647
|
+
ears: "pixie",
|
|
648
|
+
face: { eyes: "dot", mouth: "smile", muzzle: "none", cheeks: "#f0857a" },
|
|
649
|
+
tail: "none",
|
|
650
|
+
headExtras: ["bun", "flower-crown"],
|
|
651
|
+
bodyExtras: ["wings-fairy", "petals", "wand"],
|
|
652
|
+
extraColors: ["#c4ecff", "#f7a8cf", "#fff3a0", "#ffffff"]
|
|
619
653
|
}
|
|
620
654
|
];
|
|
621
655
|
|
|
@@ -852,6 +886,7 @@ function headShapes(def, ow) {
|
|
|
852
886
|
if (has("locks")) {
|
|
853
887
|
out.push({ k: "path", d: smoothClosed(pts(-1.14, 0.58, -1.24, -0.2, -0.86, -0.98, 0, -1.2, 0.86, -0.98, 1.24, -0.2, 1.14, 0.58, 0.8, 0.66, 0, 0.2, -0.8, 0.66)), fill: p.hair ?? p.outline, ...ol });
|
|
854
888
|
}
|
|
889
|
+
if (has("bun")) out.push({ k: "circle", x: 0.04, y: -1.14, r: 0.4, fill: p.hair ?? p.outline, ...ol });
|
|
855
890
|
if (has("ninja-hood")) {
|
|
856
891
|
for (const [rot, len3] of [
|
|
857
892
|
[0.35, 0.5],
|
|
@@ -889,7 +924,15 @@ function headShapes(def, ow) {
|
|
|
889
924
|
if (muzzle === "disc") for (const s of SIDES) out.push({ k: "circle", x: s * 0.4, y: 0.02, r: 0.44, fill: mc });
|
|
890
925
|
if (muzzle === "cheeks") for (const s of SIDES) out.push({ k: "ellipse", x: s * 0.4, y: 0.44, rx: 0.44, ry: 0.32, fill: mc });
|
|
891
926
|
if (muzzle === "band") out.push({ k: "path", d: lowerFace(def, ow), fill: mc });
|
|
892
|
-
if (has("stripes")) {
|
|
927
|
+
if (has("stripes") && def.head === "snout-long") {
|
|
928
|
+
const c = extra(0, p.outline);
|
|
929
|
+
for (const [x0, y0, x1, y1] of [
|
|
930
|
+
[0.46, -0.94, 0.36, -0.66],
|
|
931
|
+
[0.8, -0.72, 0.62, -0.5],
|
|
932
|
+
[1, -0.36, 0.78, -0.24]
|
|
933
|
+
])
|
|
934
|
+
out.push({ k: "line", pts: [x0, y0, x1, y1], stroke: c, lw: 0.11 });
|
|
935
|
+
} else if (has("stripes")) {
|
|
893
936
|
const c = extra(0, p.outline);
|
|
894
937
|
out.push({ k: "line", pts: [0, -0.97, 0, -0.66], stroke: c, lw: 0.13 });
|
|
895
938
|
for (const s of SIDES) {
|
|
@@ -904,6 +947,13 @@ function headShapes(def, ow) {
|
|
|
904
947
|
out.push({ k: "circle", x: 0.58, y: -0.42, r: 0.11, fill: c });
|
|
905
948
|
out.push({ k: "circle", x: 0.22, y: -0.76, r: 0.09, fill: c });
|
|
906
949
|
}
|
|
950
|
+
if (def.head === "snout-long") {
|
|
951
|
+
for (const [x, y, r] of [
|
|
952
|
+
[-1.38, -0.14, 0.06],
|
|
953
|
+
[-1.14, -0.22, 0.055]
|
|
954
|
+
])
|
|
955
|
+
out.push({ k: "ellipse", x, y, rx: r, ry: r * 0.8, rot: -0.3, fill: p.outline, alpha: 0.85 });
|
|
956
|
+
}
|
|
907
957
|
if (has("forehead")) out.push({ k: "path", d: smoothClosed(pts(-0.62, -0.66, 0, -0.86, 0.62, -0.66, 0.26, -0.36, 0, -0.1, -0.26, -0.36), 0.9), fill: extra(0, p.accent) });
|
|
908
958
|
if (has("eye-spot")) out.push({ k: "ellipse", x: 0.42, y: -0.06, rx: 0.3, ry: 0.33, rot: 0.3, fill: p.ears ?? p.accent });
|
|
909
959
|
if (has("gills")) {
|
|
@@ -912,6 +962,7 @@ function headShapes(def, ow) {
|
|
|
912
962
|
const hair = p.hair ?? p.outline;
|
|
913
963
|
if (has("hair")) out.push({ k: "path", d: smoothClosed(pts(-1.02, -0.06, -1.02, -0.58, -0.62, -1.04, 0.08, -1.14, 0.76, -0.92, 1.05, -0.42, 1, -0.06, 0.84, -0.4, 0.42, -0.5, 0.06, -0.4, -0.34, -0.56, -0.74, -0.38)), fill: hair, ...ol });
|
|
914
964
|
if (has("locks")) out.push({ k: "path", d: smoothClosed(pts(-1, -0.08, -0.98, -0.62, -0.56, -1.02, 0, -1.1, 0.56, -1.02, 0.98, -0.62, 1, -0.08, 0.8, -0.38, 0.36, -0.44, 0, -0.4, -0.36, -0.44, -0.8, -0.38)), fill: hair, ...ol });
|
|
965
|
+
if (has("bun")) out.push({ k: "path", d: smoothClosed(pts(-1.02, -0.1, -0.98, -0.62, -0.56, -1.02, 0, -1.1, 0.56, -1.02, 0.98, -0.62, 1.02, -0.1, 0.84, -0.36, 0.5, -0.56, 0.12, -0.48, -0.22, -0.6, -0.6, -0.5, -0.86, -0.36)), fill: hair, ...ol });
|
|
915
966
|
if (has("curls")) {
|
|
916
967
|
for (let i = 0; i < 7; i++) {
|
|
917
968
|
const c = polar(-1.3 + i / 6 * 2.6, 0.86);
|
|
@@ -933,7 +984,7 @@ function headShapes(def, ow) {
|
|
|
933
984
|
}
|
|
934
985
|
if (def.face.brows) for (const s of SIDES) out.push({ k: "line", pts: [s * 0.2, -0.2, s * 0.54, -0.28], stroke: def.face.brows, lw: 0.09 });
|
|
935
986
|
out.push(...mouth(def, ow));
|
|
936
|
-
if (def.face.cheeks) for (const
|
|
987
|
+
if (def.face.cheeks) for (const c of cheekSpots(def)) out.push({ k: "ellipse", x: c.x, y: c.y, rx: 0.15, ry: 0.1, fill: def.face.cheeks, alpha: 0.8 });
|
|
937
988
|
if (has("whiskers")) {
|
|
938
989
|
for (const s of SIDES)
|
|
939
990
|
for (const dy of [-0.1, 0.06]) out.push({ k: "line", pts: [s * 0.62, 0.38 + dy, s * 1.06, 0.3 + dy * 2.2], stroke: p.outline, lw: ow * 0.7, alpha: 0.75 });
|
|
@@ -955,6 +1006,24 @@ function headShapes(def, ow) {
|
|
|
955
1006
|
out.push({ k: "line", pts: [-w2, y + 0.04, w2, y - 0.04], stroke: p.outline, lw: ow * 0.7, alpha: 0.6 });
|
|
956
1007
|
}
|
|
957
1008
|
}
|
|
1009
|
+
if (has("flower-crown")) {
|
|
1010
|
+
const cols2 = [extra(1, "#f7a8cf"), extra(2, "#fff3a0"), extra(3, WHITE)];
|
|
1011
|
+
for (const a of [-0.84, -0.28, 0.28, 0.84]) {
|
|
1012
|
+
const c = polar(a, 0.9);
|
|
1013
|
+
out.push({ k: "ellipse", x: c.x, y: c.y, rx: 0.14, ry: 0.07, rot: a + 0.5, fill: "#7cc76a", stroke: p.outline, lw: ow * 0.7 });
|
|
1014
|
+
}
|
|
1015
|
+
;
|
|
1016
|
+
[-1.12, -0.56, 0, 0.56, 1.12].forEach((a, i) => {
|
|
1017
|
+
const c = polar(a, 0.9);
|
|
1018
|
+
const r = i === 2 ? 0.13 : 0.11;
|
|
1019
|
+
for (let k = 0; k < 5; k++) {
|
|
1020
|
+
const t = a + k / 5 * Math.PI * 2;
|
|
1021
|
+
out.push({ k: "circle", x: c.x + Math.sin(t) * r, y: c.y - Math.cos(t) * r, r: r * 0.82, fill: cols2[i % cols2.length], stroke: p.outline, lw: ow * 0.7 });
|
|
1022
|
+
}
|
|
1023
|
+
out.push({ k: "circle", x: c.x, y: c.y, r: r * 0.62, fill: cols2[i % cols2.length] });
|
|
1024
|
+
out.push({ k: "circle", x: c.x, y: c.y, r: r * 0.42, fill: p.accent });
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
958
1027
|
if (has("helmet")) {
|
|
959
1028
|
for (const s of SIDES) out.push({ k: "rect", x: s * 1, y: 0.05, w: 0.26, h: 0.56, r: 0.12, fill: p.accent, ...ol });
|
|
960
1029
|
out.push({ k: "path", d: "M-0.62 -0.62 Q-0.3 -0.92 0.12 -0.9", stroke: WHITE, lw: 0.1, alpha: 0.9 });
|
|
@@ -1019,6 +1088,8 @@ function headShape(def, ol) {
|
|
|
1019
1088
|
return { k: "rect", x: 0, y: 0, w: 2, h: 1.8, r: 0.46, fill, ...ol };
|
|
1020
1089
|
case "bucket":
|
|
1021
1090
|
return { k: "path", d: roundedPolygon(pts(-0.94, 0.96, -0.98, -0.36, -0.62, -1.04, 0.62, -1.04, 0.98, -0.36, 0.94, 0.96), 0.42), fill, ...ol };
|
|
1091
|
+
case "snout-long":
|
|
1092
|
+
return { k: "path", d: smoothClosed(SNOUT_HEAD, 0.95), fill, ...ol };
|
|
1022
1093
|
case "bulb":
|
|
1023
1094
|
return {
|
|
1024
1095
|
k: "path",
|
|
@@ -1037,6 +1108,19 @@ function headShape(def, ol) {
|
|
|
1037
1108
|
};
|
|
1038
1109
|
}
|
|
1039
1110
|
}
|
|
1111
|
+
var SNOUT_HEAD = pts(0.7, -0.88, 0.12, -1.04, -0.3, -0.98, -0.72, -0.74, -1.2, -0.56, -1.5, -0.32, -1.6, 0.04, -1.44, 0.42, -0.9, 0.72, -0.2, 0.92, 0.5, 0.88, 0.96, 0.56, 1.08, 0, 0.98, -0.5);
|
|
1112
|
+
function eyeSpots(def, ex, ey) {
|
|
1113
|
+
if (def.head === "snout-long")
|
|
1114
|
+
return [
|
|
1115
|
+
{ x: -0.36, y: -0.5, k: 0.95 },
|
|
1116
|
+
{ x: 0.26, y: -0.48, k: 1.1 }
|
|
1117
|
+
];
|
|
1118
|
+
return SIDES.map((s) => ({ x: s * ex, y: ey, k: 1 }));
|
|
1119
|
+
}
|
|
1120
|
+
function cheekSpots(def) {
|
|
1121
|
+
if (def.head === "snout-long") return pts(-0.66, -0.14, 0.66, 0.1);
|
|
1122
|
+
return SIDES.map((s) => ({ x: s * 0.64, y: 0.3 }));
|
|
1123
|
+
}
|
|
1040
1124
|
function headEllipse(def) {
|
|
1041
1125
|
if (def.head === "round") return { cy: 0, rx: 1, ry: 1 };
|
|
1042
1126
|
if (def.head === "tall") return { cy: -0.02, rx: 0.94, ry: 1.04 };
|
|
@@ -1117,6 +1201,10 @@ function ears(def, ow) {
|
|
|
1117
1201
|
out.push({ k: "ellipse", x: s * 1.04, y: -0.22, rx: 0.4, ry: 0.18, rot: s * 0.32, fill: p.skin ?? p.body, ...ol });
|
|
1118
1202
|
out.push({ k: "ellipse", x: s * 1.08, y: -0.2, rx: 0.24, ry: 0.08, rot: s * 0.32, fill: p.accent });
|
|
1119
1203
|
break;
|
|
1204
|
+
case "pixie":
|
|
1205
|
+
out.push({ k: "path", d: roundedPolygon(pts(s * 0.86, -0.22, s * 1.36, -0.5, s * 1.02, 0.26), 0.28), fill: p.skin ?? p.body, ...ol });
|
|
1206
|
+
out.push({ k: "line", pts: [s * 0.98, -0.12, s * 1.16, -0.3], stroke: p.accent, lw: ow * 0.8, alpha: 0.5 });
|
|
1207
|
+
break;
|
|
1120
1208
|
case "human":
|
|
1121
1209
|
out.push({ k: "ellipse", x: s * 0.98, y: 0.06, rx: 0.17, ry: 0.23, fill: p.skin ?? p.body, ...ol });
|
|
1122
1210
|
break;
|
|
@@ -1149,15 +1237,15 @@ function eyes(def, ow) {
|
|
|
1149
1237
|
const shine = (x, y, r) => ({ k: "circle", x, y, r, fill: WHITE });
|
|
1150
1238
|
switch (f2.eyes) {
|
|
1151
1239
|
case "dot":
|
|
1152
|
-
for (const
|
|
1153
|
-
out.push({ k: "ellipse", x:
|
|
1154
|
-
out.push(shine(
|
|
1240
|
+
for (const e of eyeSpots(def, ex, ey)) {
|
|
1241
|
+
out.push({ k: "ellipse", x: e.x, y: e.y, rx: 0.12 * e.k, ry: 0.155 * e.k, fill: ink });
|
|
1242
|
+
out.push(shine(e.x + 0.04 * e.k, e.y - 0.06 * e.k, 0.045 * e.k));
|
|
1155
1243
|
}
|
|
1156
1244
|
break;
|
|
1157
1245
|
case "bead":
|
|
1158
|
-
for (const
|
|
1159
|
-
out.push({ k: "ellipse", x:
|
|
1160
|
-
out.push(shine(
|
|
1246
|
+
for (const e of eyeSpots(def, ex, ey)) {
|
|
1247
|
+
out.push({ k: "ellipse", x: e.x, y: e.y, rx: 0.16 * e.k, ry: 0.22 * e.k, fill: ink });
|
|
1248
|
+
out.push(shine(e.x + 0.05 * e.k, e.y - 0.08 * e.k, 0.065 * e.k));
|
|
1161
1249
|
}
|
|
1162
1250
|
break;
|
|
1163
1251
|
case "big":
|
|
@@ -1266,6 +1354,21 @@ function mouth(def, ow) {
|
|
|
1266
1354
|
out.push({ k: "ellipse", x: 0, y: 0.48, rx: 0.16, ry: 0.11, fill: MOUTH });
|
|
1267
1355
|
out.push({ k: "path", d: "M-0.3 0.12 Q0 -0.02 0.3 0.12 Q0.36 0.52 0.04 0.76 Q0.08 0.52 -0.08 0.46 Q-0.28 0.38 -0.3 0.12 Z", fill: p.accent, stroke: p.outline, lw: ow * 0.8 });
|
|
1268
1356
|
break;
|
|
1357
|
+
case "toothy": {
|
|
1358
|
+
out.push({ k: "path", d: "M-1.4 0.1 Q-0.5 0.36 0.5 0.2 Q0.64 0.16 0.68 0.02 Q0.78 0.28 0.52 0.38 Q-0.4 0.78 -1.3 0.26 Z", fill: dark, stroke: dark, lw: ow * 0.6 });
|
|
1359
|
+
out.push({ k: "ellipse", x: -0.38, y: 0.5, rx: 0.24, ry: 0.08, fill: TONGUE });
|
|
1360
|
+
for (const t of [0.12, 0.27, 0.42, 0.57, 0.72, 0.86]) {
|
|
1361
|
+
const x = (1 - t) * (1 - t) * -1.4 + 2 * (1 - t) * t * -0.5 + t * t * 0.5;
|
|
1362
|
+
const y = (1 - t) * (1 - t) * 0.1 + 2 * (1 - t) * t * 0.36 + t * t * 0.2 - 0.01;
|
|
1363
|
+
out.push({ k: "path", d: roundedPolygon(pts(x - 0.08, y, x + 0.08, y, x, y + 0.14), 0.3), fill: WHITE });
|
|
1364
|
+
}
|
|
1365
|
+
for (const t of [0.3, 0.7]) {
|
|
1366
|
+
const x = (1 - t) * (1 - t) * 0.52 + 2 * (1 - t) * t * -0.4 + t * t * -1.3;
|
|
1367
|
+
const y = (1 - t) * (1 - t) * 0.38 + 2 * (1 - t) * t * 0.78 + t * t * 0.26 + 0.01;
|
|
1368
|
+
out.push({ k: "path", d: roundedPolygon(pts(x - 0.07, y, x + 0.07, y, x, y - 0.12), 0.3), fill: WHITE });
|
|
1369
|
+
}
|
|
1370
|
+
break;
|
|
1371
|
+
}
|
|
1269
1372
|
case "jaws": {
|
|
1270
1373
|
out.push({ k: "path", d: "M-0.5 0.3 Q0 0.5 0.5 0.3 Q0.38 0.72 0 0.72 Q-0.38 0.72 -0.5 0.3 Z", fill: dark, stroke: dark, lw: ow * 0.5 });
|
|
1271
1374
|
for (const x of [-0.34, -0.17, 0, 0.17, 0.34]) {
|
|
@@ -1288,7 +1391,10 @@ var BAND_BODY = {
|
|
|
1288
1391
|
"9-12": { head: 0.5, neck: 0.92, arm: 0.17, leg: 0.21, hand: 0.125, foot: 0.9, shoulderMin: 0.36, shoulderPad: 0.07, hipMin: 0.32, hipPad: 0.08, bottom: 0.1, bulge: 1, armsOverHead: false }
|
|
1289
1392
|
};
|
|
1290
1393
|
var DEFAULT_BAND = "6-8";
|
|
1291
|
-
var bodyBand = (def) =>
|
|
1394
|
+
var bodyBand = (def, shown) => {
|
|
1395
|
+
if (shown !== void 0 && def.band !== "all" && def.alsoIn?.includes(shown) && shown in BAND_BODY) return shown;
|
|
1396
|
+
return def.band in BAND_BODY ? def.band : DEFAULT_BAND;
|
|
1397
|
+
};
|
|
1292
1398
|
var HEAD = BAND_BODY[DEFAULT_BAND].head;
|
|
1293
1399
|
var MAX_TILT = 0.3;
|
|
1294
1400
|
var TILT_DEAD_ZONE = 0.12;
|
|
@@ -1382,7 +1488,7 @@ function headTilt(body, at, angle, U) {
|
|
|
1382
1488
|
return 0;
|
|
1383
1489
|
}
|
|
1384
1490
|
function avatarShapes(body, def, box, opts = {}) {
|
|
1385
|
-
const band = bodyBand(def);
|
|
1491
|
+
const band = bodyBand(def, opts.band);
|
|
1386
1492
|
const bb = BAND_BODY[band];
|
|
1387
1493
|
const F = frameOf(body, box, { ...opts, band });
|
|
1388
1494
|
if (!F) return [];
|
|
@@ -1414,8 +1520,8 @@ function avatarShapes(body, def, box, opts = {}) {
|
|
|
1414
1520
|
out.push({ k: "line", pts: flat2, stroke: p.outline, lw: w2 + ow * 2 });
|
|
1415
1521
|
out.push({ k: "line", pts: flat2, stroke: color, lw: w2 });
|
|
1416
1522
|
};
|
|
1417
|
-
const legW = bb.leg * U;
|
|
1418
|
-
const armW = bb.arm * U;
|
|
1523
|
+
const legW = bb.leg * U * (has("strong-legs") ? 1.3 : 1);
|
|
1524
|
+
const armW = bb.arm * U * (has("tiny-arms") ? 0.6 : 1);
|
|
1419
1525
|
const pad = Math.max(bb.arm / 0.22, 0.9);
|
|
1420
1526
|
if (F.hipsOk) {
|
|
1421
1527
|
for (const [hip, kneeI, ankleI, side] of [
|
|
@@ -1429,7 +1535,7 @@ function avatarShapes(body, def, box, opts = {}) {
|
|
|
1429
1535
|
const pts2 = ankle.ok ? [top, knee, ankle] : [top, knee];
|
|
1430
1536
|
limb(legs, pts2, legW, p.limbs);
|
|
1431
1537
|
if (has("joints")) legs.push({ k: "circle", x: knee.x, y: knee.y, r: 0.15 * U * pad, fill: p.accent, ...ol });
|
|
1432
|
-
if (ankle.ok) legs.push(
|
|
1538
|
+
if (ankle.ok) legs.push(...feet(def, ankle, F, side, ow, bb.foot));
|
|
1433
1539
|
}
|
|
1434
1540
|
}
|
|
1435
1541
|
for (const [sh, elbowI, wristI, side] of [
|
|
@@ -1462,7 +1568,20 @@ function avatarShapes(body, def, box, opts = {}) {
|
|
|
1462
1568
|
out.push({ k: "ellipse", x: q.x, y: q.y, rx: 0.2 * U, ry: 0.09 * U, rot: Math.atan2(fd.y, fd.x), fill: p.hands ?? p.accent, ...ol });
|
|
1463
1569
|
}
|
|
1464
1570
|
}
|
|
1465
|
-
|
|
1571
|
+
if (has("wand") && side === 1 && !opts.portrait) out.push(...wand(def, hand, norm(add(mul(dir, 0.4), sub(mul(r, 0.6), d))), U, ow));
|
|
1572
|
+
const handR = bb.hand * U * (has("tiny-arms") ? 0.72 : 1);
|
|
1573
|
+
if (has("tiny-arms")) {
|
|
1574
|
+
for (const a of [-0.45, 0.45]) {
|
|
1575
|
+
const c = Math.cos(a);
|
|
1576
|
+
const s = Math.sin(a);
|
|
1577
|
+
const cd = { x: dir.x * c - dir.y * s, y: dir.x * s + dir.y * c };
|
|
1578
|
+
const base = add(hand, mul(cd, handR * 0.75));
|
|
1579
|
+
const tip = add(hand, mul(cd, handR * 1.6));
|
|
1580
|
+
const nrm = { x: -cd.y * handR * 0.32, y: cd.x * handR * 0.32 };
|
|
1581
|
+
out.push({ k: "path", d: roundedPolygon([add(base, nrm), tip, sub(base, nrm)], 0.2), fill: p.belly, ...ol });
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
out.push({ k: "circle", x: hand.x, y: hand.y, r: handR, fill: p.hands ?? p.limbs, ...ol });
|
|
1466
1585
|
}
|
|
1467
1586
|
if (has("wings")) {
|
|
1468
1587
|
for (const s of [-1, 1]) {
|
|
@@ -1477,6 +1596,7 @@ function avatarShapes(body, def, box, opts = {}) {
|
|
|
1477
1596
|
back.push({ k: "path", d: roundedPolygon(w2, 0.18), fill: p.accent, ...ol });
|
|
1478
1597
|
}
|
|
1479
1598
|
}
|
|
1599
|
+
if (has("wings-fairy")) back.push(...fairyWings(def, T2, ht, U, ow));
|
|
1480
1600
|
if (has("cape")) {
|
|
1481
1601
|
const yb = L + 0.8 * U;
|
|
1482
1602
|
const wb = hb + 0.36 * U;
|
|
@@ -1694,6 +1814,7 @@ function avatarShapes(body, def, box, opts = {}) {
|
|
|
1694
1814
|
const k = 0.15 * U;
|
|
1695
1815
|
torso.push({ k: "group", x: c.x, y: c.y, rot: F.angle - 0.2, s: k, items: [{ k: "path", d: STAR, fill: def.extraColors?.[0] ?? "#ffd84a", stroke: p.outline, lw: ow * 0.8 / k }] });
|
|
1696
1816
|
}
|
|
1817
|
+
if (has("petals")) torso.push(...petals(def, T2, ht, hb, bulge, L, y1, U, ow, bb));
|
|
1697
1818
|
if (has("scarf")) {
|
|
1698
1819
|
const y = Math.max(0.04, bb.head * (1 - bb.neck) - 0.06) * U;
|
|
1699
1820
|
const c = T2(0, y);
|
|
@@ -1718,6 +1839,18 @@ function cachedHead(def, ow) {
|
|
|
1718
1839
|
}
|
|
1719
1840
|
return shapes;
|
|
1720
1841
|
}
|
|
1842
|
+
function feet(def, ankle, F, side, ow, k) {
|
|
1843
|
+
const shoe = foot(def, ankle, F, side, ow, k);
|
|
1844
|
+
if (!def.bodyExtras.includes("strong-legs")) return [shoe];
|
|
1845
|
+
const U = F.U * k;
|
|
1846
|
+
const c = add(add(ankle, mul(F.d, 0.07 * U)), mul(F.r, side * 0.06 * U));
|
|
1847
|
+
const out = [shoe];
|
|
1848
|
+
for (const t of [-1, 0, 1]) {
|
|
1849
|
+
const q = add(add(c, mul(F.r, t * 0.12 * U)), mul(F.d, 0.09 * U));
|
|
1850
|
+
out.push({ k: "ellipse", x: q.x, y: q.y, rx: 0.05 * U, ry: 0.045 * U, rot: F.angle, fill: def.palette.belly, stroke: def.palette.outline, lw: ow * 0.8 });
|
|
1851
|
+
}
|
|
1852
|
+
return out;
|
|
1853
|
+
}
|
|
1721
1854
|
function foot(def, ankle, F, side, ow, k) {
|
|
1722
1855
|
const p = def.palette;
|
|
1723
1856
|
const { d, r } = F;
|
|
@@ -1871,6 +2004,30 @@ function tail(def, F, hb, ow) {
|
|
|
1871
2004
|
});
|
|
1872
2005
|
break;
|
|
1873
2006
|
}
|
|
2007
|
+
case "thick": {
|
|
2008
|
+
const pts2 = TP([
|
|
2009
|
+
[hb * 0.3, L * 0.42],
|
|
2010
|
+
[hb + 0.42 * U, L * 0.62],
|
|
2011
|
+
[hb + 0.92 * U, L * 0.96],
|
|
2012
|
+
[hb + 1.32 * U, L * 1.08],
|
|
2013
|
+
[hb + 1.44 * U, L * 0.98],
|
|
2014
|
+
[hb + 0.98 * U, L * 1.2],
|
|
2015
|
+
[hb + 0.4 * U, L * 1.22],
|
|
2016
|
+
[hb * 0.3, L * 1.12]
|
|
2017
|
+
]);
|
|
2018
|
+
out.push({ k: "path", d: smoothClosed(pts2, 0.8), fill: p.body, ...ol });
|
|
2019
|
+
const c = def.extraColors?.[0] ?? p.accent;
|
|
2020
|
+
for (const [x, y, l] of [
|
|
2021
|
+
[0.28, 0.58, 0.26],
|
|
2022
|
+
[0.62, 0.8, 0.22],
|
|
2023
|
+
[0.96, 1, 0.16]
|
|
2024
|
+
]) {
|
|
2025
|
+
const a = T2(hb + x * U, L * y + 0.03 * U);
|
|
2026
|
+
const b = T2(hb + (x - 0.1) * U, L * y + l * U);
|
|
2027
|
+
out.push({ k: "line", pts: [a.x, a.y, b.x, b.y], stroke: c, lw: 0.09 * U });
|
|
2028
|
+
}
|
|
2029
|
+
break;
|
|
2030
|
+
}
|
|
1874
2031
|
case "spiky":
|
|
1875
2032
|
case "arrow": {
|
|
1876
2033
|
const pts2 = TP([
|
|
@@ -1903,6 +2060,81 @@ function tail(def, F, hb, ow) {
|
|
|
1903
2060
|
}
|
|
1904
2061
|
return out;
|
|
1905
2062
|
}
|
|
2063
|
+
function wand(def, hand, wd, U, ow) {
|
|
2064
|
+
const p = def.palette;
|
|
2065
|
+
const from = add(hand, mul(wd, -0.08 * U));
|
|
2066
|
+
const tip = add(hand, mul(wd, 0.52 * U));
|
|
2067
|
+
const k = 0.2 * U;
|
|
2068
|
+
return [
|
|
2069
|
+
{ k: "line", pts: [from.x, from.y, tip.x, tip.y], stroke: p.outline, lw: 0.06 * U + ow * 2 },
|
|
2070
|
+
{ k: "line", pts: [from.x, from.y, tip.x, tip.y], stroke: "#fff3d6", lw: 0.06 * U },
|
|
2071
|
+
{ k: "circle", x: tip.x, y: tip.y, r: 0.24 * U, fill: p.accent, alpha: 0.28 },
|
|
2072
|
+
{ k: "group", x: tip.x, y: tip.y, rot: Math.atan2(wd.y, wd.x) + Math.PI / 2, s: k, items: [{ k: "path", d: STAR, fill: p.accent, stroke: p.outline, lw: ow * 0.9 / k }] }
|
|
2073
|
+
];
|
|
2074
|
+
}
|
|
2075
|
+
function fairyWings(def, T2, ht, U, ow) {
|
|
2076
|
+
const p = def.palette;
|
|
2077
|
+
const fill = def.extraColors?.[0] ?? "#cdeeff";
|
|
2078
|
+
const out = [];
|
|
2079
|
+
for (const s of [-1, 1]) {
|
|
2080
|
+
const lobes = [
|
|
2081
|
+
// Upper: big, up and out.
|
|
2082
|
+
[
|
|
2083
|
+
[s * ht * 0.3, 0.12 * U],
|
|
2084
|
+
[s * (ht + 0.42 * U), -0.66 * U],
|
|
2085
|
+
[s * (ht + 1 * U), -0.74 * U],
|
|
2086
|
+
[s * (ht + 1.02 * U), -0.18 * U],
|
|
2087
|
+
[s * (ht + 0.5 * U), 0.26 * U]
|
|
2088
|
+
],
|
|
2089
|
+
// Lower: smaller, down and out.
|
|
2090
|
+
[
|
|
2091
|
+
[s * ht * 0.4, 0.3 * U],
|
|
2092
|
+
[s * (ht + 0.6 * U), 0.36 * U],
|
|
2093
|
+
[s * (ht + 0.74 * U), 0.78 * U],
|
|
2094
|
+
[s * (ht + 0.3 * U), 0.9 * U]
|
|
2095
|
+
]
|
|
2096
|
+
];
|
|
2097
|
+
for (const lobe of lobes) {
|
|
2098
|
+
const d = smoothClosed(lobe.map(([x, y]) => T2(x, y)), 0.9);
|
|
2099
|
+
out.push({ k: "path", d, fill, alpha: 0.6 });
|
|
2100
|
+
out.push({ k: "path", d, stroke: p.outline, lw: ow * 0.9 });
|
|
2101
|
+
const inner = lobe[Math.floor(lobe.length / 2)];
|
|
2102
|
+
const a = T2(lobe[0][0], lobe[0][1]);
|
|
2103
|
+
const b = T2(lobe[0][0] + (inner[0] - lobe[0][0]) * 0.75, lobe[0][1] + (inner[1] - lobe[0][1]) * 0.75);
|
|
2104
|
+
out.push({ k: "line", pts: [a.x, a.y, b.x, b.y], stroke: "#ffffff", lw: ow * 0.9, alpha: 0.85 });
|
|
2105
|
+
}
|
|
2106
|
+
for (const [x, y, r] of [
|
|
2107
|
+
[ht + 0.72 * U, -0.42 * U, 0.075 * U],
|
|
2108
|
+
[ht + 0.44 * U, -0.16 * U, 0.05 * U],
|
|
2109
|
+
[ht + 0.5 * U, 0.6 * U, 0.055 * U]
|
|
2110
|
+
]) {
|
|
2111
|
+
const q = T2(s * x, y);
|
|
2112
|
+
out.push({ k: "group", x: q.x, y: q.y, s: r, items: [{ k: "path", d: STAR, fill: "#ffffff" }] });
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
return out;
|
|
2116
|
+
}
|
|
2117
|
+
function petals(def, T2, ht, hb, bulge, L, y1, U, ow, bb) {
|
|
2118
|
+
const p = def.palette;
|
|
2119
|
+
const second = def.extraColors?.[1] ?? p.belly;
|
|
2120
|
+
const out = [];
|
|
2121
|
+
const ang = Math.atan2(T2(1, 0).y - T2(0, 0).y, T2(1, 0).x - T2(0, 0).x);
|
|
2122
|
+
const n = 5;
|
|
2123
|
+
const w2 = Math.max(hb, bulge * 0.92) * 1.08;
|
|
2124
|
+
const y = (L * 0.8 + y1) / 2;
|
|
2125
|
+
const order = [0, 4, 1, 3, 2];
|
|
2126
|
+
for (const i of order) {
|
|
2127
|
+
const t = i / (n - 1) - 0.5;
|
|
2128
|
+
const c = T2(t * 2 * w2, y + (0.5 - Math.abs(t)) * 0.1 * U);
|
|
2129
|
+
out.push({ k: "ellipse", x: c.x, y: c.y, rx: w2 * 0.3, ry: 0.34 * U, rot: ang - t * 0.6, fill: i % 2 ? second : p.body, stroke: p.outline, lw: ow });
|
|
2130
|
+
}
|
|
2131
|
+
const cy = Math.max(0.04, bb.head * (1 - bb.neck) - 0.04) * U;
|
|
2132
|
+
for (const t of [-1, 1, 0]) {
|
|
2133
|
+
const c = T2(t * ht * 0.32, cy + (t ? 0 : 0.05 * U));
|
|
2134
|
+
out.push({ k: "ellipse", x: c.x, y: c.y, rx: ht * 0.26, ry: 0.12 * U, rot: ang + t * 0.5, fill: t ? second : p.body, stroke: p.outline, lw: ow * 0.9 });
|
|
2135
|
+
}
|
|
2136
|
+
return out;
|
|
2137
|
+
}
|
|
1906
2138
|
|
|
1907
2139
|
// ../avatars/src/poses.ts
|
|
1908
2140
|
var STAND = {
|
|
@@ -2045,17 +2277,19 @@ var DEFAULT_AVATAR = BY_ID.get("fox");
|
|
|
2045
2277
|
var isAvatarId = (v) => typeof v === "string" && BY_ID.has(v);
|
|
2046
2278
|
var avatarById = (id) => BY_ID.get(id);
|
|
2047
2279
|
var resolve = (def) => typeof def === "string" ? BY_ID.get(def) ?? DEFAULT_AVATAR : def;
|
|
2048
|
-
function iconShapes(def, size) {
|
|
2280
|
+
function iconShapes(def, size, band) {
|
|
2049
2281
|
const d = resolve(def);
|
|
2050
2282
|
const body = neutralPose();
|
|
2051
|
-
const
|
|
2052
|
-
const unit = frameOf(body, { x: 0, y: 0, w: 1, h: 1 }, { band });
|
|
2053
|
-
const
|
|
2283
|
+
const shown = bodyBand(d, band);
|
|
2284
|
+
const unit = frameOf(body, { x: 0, y: 0, w: 1, h: 1 }, { band: shown });
|
|
2285
|
+
const hb = bounds(headShapes(d, 0.05));
|
|
2286
|
+
const top = -hb.minY;
|
|
2054
2287
|
const headR = Math.min(0.27, 0.43 / Math.max(top, 1)) * size;
|
|
2055
|
-
const
|
|
2288
|
+
const dx = d.head === "snout-long" ? (hb.minX + hb.maxX) / 2 * headR : 0;
|
|
2289
|
+
const k = headR / BAND_BODY[shown].head / unit.U;
|
|
2056
2290
|
const cy = 0.5 * size + Math.min(0, (top - 1.6) * 0.06 * size);
|
|
2057
|
-
const box = { x: size / 2 - unit.head.x * k, y: cy - unit.head.y * k, w: k, h: k };
|
|
2058
|
-
return avatarShapes(body, d, box, { portrait: true });
|
|
2291
|
+
const box = { x: size / 2 - dx - unit.head.x * k, y: cy - unit.head.y * k, w: k, h: k };
|
|
2292
|
+
return avatarShapes(body, d, box, { portrait: true, band: shown });
|
|
2059
2293
|
}
|
|
2060
2294
|
function smoothBody(prev, next, amount = 0.5) {
|
|
2061
2295
|
if (!prev || prev.length !== next.length) return next.slice();
|
|
@@ -7369,6 +7603,10 @@ async function createEngine(opts = {}) {
|
|
|
7369
7603
|
};
|
|
7370
7604
|
unlisten.push(
|
|
7371
7605
|
play.on("start", (m) => lateHint(m?.device)),
|
|
7606
|
+
// The simple API knows muted only from the start (its getter is false before): read it again then.
|
|
7607
|
+
play.on("start", (m) => {
|
|
7608
|
+
if (typeof m?.muted === "boolean") sound.muted = m.muted;
|
|
7609
|
+
}),
|
|
7372
7610
|
play.on("frame", (m) => setPlayers(m?.players)),
|
|
7373
7611
|
play.on("players", (m) => setPlayers(m?.players)),
|
|
7374
7612
|
play.on("backdrop", (m) => setBackdrop(m?.backdrop, m?.view)),
|