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