@voqalize/avatar 0.4.4 → 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/README.md +65 -154
- package/dist/index.d.ts +37 -26
- package/dist/index.js +48 -25
- package/dist/pin.d.ts +2 -0
- package/dist/pin.js +3 -0
- package/dist/react.d.ts +21 -6
- package/dist/react.js +24 -6
- package/package.json +8 -67
- package/LICENSE-APACHE-2.0 +0 -201
- package/LICENSE-CC-BY-4.0 +0 -396
- package/assets/README.md +0 -60
- package/assets/tanvi.glb +0 -0
- package/assets/tanya.glb +0 -0
- package/assets/tara.glb +0 -0
- package/assets/tess.glb +0 -0
- package/assets/tushar.glb +0 -0
- package/client/Avatar.tsx +0 -57
- package/client/AvatarClient.ts +0 -818
- package/client/createAvatar.ts +0 -177
- package/client/index.ts +0 -44
- package/client/internal.ts +0 -132
- package/client/playout.ts +0 -95
- package/client/react.ts +0 -13
- package/client/supports.ts +0 -27
- package/client/three/assets.ts +0 -26
- package/client/three/budgets.ts +0 -27
- package/client/three/character-rig.ts +0 -1746
- package/client/three/createCharacter.ts +0 -109
- package/client/three/holds.ts +0 -69
- package/client/three/internal.ts +0 -55
- package/client/three/motion-limits.json +0 -124
- package/client/three/sequences.ts +0 -281
- package/client/three/tanvi-asset.ts +0 -6
- package/client/three/tanvi.ts +0 -30
- package/client/three/tanya-asset.ts +0 -6
- package/client/three/tanya.ts +0 -29
- package/client/three/tara-asset.ts +0 -25
- package/client/three/tara.ts +0 -36
- package/client/three/tess-asset.ts +0 -6
- package/client/three/tess.ts +0 -30
- package/client/three/tushar-asset.ts +0 -6
- package/client/three/tushar.ts +0 -28
- package/client/types.ts +0 -235
- package/client/useAvatar.ts +0 -74
- package/dist/Avatar.d.ts +0 -44
- package/dist/Avatar.d.ts.map +0 -1
- package/dist/Avatar.js +0 -7
- package/dist/Avatar.js.map +0 -1
- package/dist/AvatarClient.d.ts +0 -274
- package/dist/AvatarClient.d.ts.map +0 -1
- package/dist/AvatarClient.js +0 -712
- package/dist/AvatarClient.js.map +0 -1
- package/dist/createAvatar.d.ts +0 -135
- package/dist/createAvatar.d.ts.map +0 -1
- package/dist/createAvatar.js +0 -75
- package/dist/createAvatar.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/internal.d.ts +0 -34
- package/dist/internal.d.ts.map +0 -1
- package/dist/internal.js +0 -56
- package/dist/internal.js.map +0 -1
- package/dist/playout.d.ts +0 -42
- package/dist/playout.d.ts.map +0 -1
- package/dist/playout.js +0 -85
- package/dist/playout.js.map +0 -1
- package/dist/react.d.ts.map +0 -1
- package/dist/react.js.map +0 -1
- package/dist/supports.d.ts +0 -19
- package/dist/supports.d.ts.map +0 -1
- package/dist/supports.js +0 -25
- package/dist/supports.js.map +0 -1
- package/dist/three/assets.d.ts +0 -10
- package/dist/three/assets.d.ts.map +0 -1
- package/dist/three/assets.js +0 -23
- package/dist/three/assets.js.map +0 -1
- package/dist/three/budgets.d.ts +0 -17
- package/dist/three/budgets.d.ts.map +0 -1
- package/dist/three/budgets.js +0 -21
- package/dist/three/budgets.js.map +0 -1
- package/dist/three/character-rig.d.ts +0 -366
- package/dist/three/character-rig.d.ts.map +0 -1
- package/dist/three/character-rig.js +0 -1644
- package/dist/three/character-rig.js.map +0 -1
- package/dist/three/createCharacter.d.ts +0 -60
- package/dist/three/createCharacter.d.ts.map +0 -1
- package/dist/three/createCharacter.js +0 -84
- package/dist/three/createCharacter.js.map +0 -1
- package/dist/three/holds.d.ts +0 -38
- package/dist/three/holds.d.ts.map +0 -1
- package/dist/three/holds.js +0 -61
- package/dist/three/holds.js.map +0 -1
- package/dist/three/internal.d.ts +0 -52
- package/dist/three/internal.d.ts.map +0 -1
- package/dist/three/internal.js +0 -49
- package/dist/three/internal.js.map +0 -1
- package/dist/three/motion-limits.json +0 -124
- package/dist/three/sequences.d.ts +0 -71
- package/dist/three/sequences.d.ts.map +0 -1
- package/dist/three/sequences.js +0 -262
- package/dist/three/sequences.js.map +0 -1
- package/dist/three/tanvi-asset.d.ts +0 -7
- package/dist/three/tanvi-asset.d.ts.map +0 -1
- package/dist/three/tanvi-asset.js +0 -7
- package/dist/three/tanvi-asset.js.map +0 -1
- package/dist/three/tanvi.d.ts +0 -24
- package/dist/three/tanvi.d.ts.map +0 -1
- package/dist/three/tanvi.js +0 -24
- package/dist/three/tanvi.js.map +0 -1
- package/dist/three/tanya-asset.d.ts +0 -7
- package/dist/three/tanya-asset.d.ts.map +0 -1
- package/dist/three/tanya-asset.js +0 -7
- package/dist/three/tanya-asset.js.map +0 -1
- package/dist/three/tanya.d.ts +0 -23
- package/dist/three/tanya.d.ts.map +0 -1
- package/dist/three/tanya.js +0 -23
- package/dist/three/tanya.js.map +0 -1
- package/dist/three/tara-asset.d.ts +0 -26
- package/dist/three/tara-asset.d.ts.map +0 -1
- package/dist/three/tara-asset.js +0 -26
- package/dist/three/tara-asset.js.map +0 -1
- package/dist/three/tara.d.ts +0 -30
- package/dist/three/tara.d.ts.map +0 -1
- package/dist/three/tara.js +0 -30
- package/dist/three/tara.js.map +0 -1
- package/dist/three/tess-asset.d.ts +0 -7
- package/dist/three/tess-asset.d.ts.map +0 -1
- package/dist/three/tess-asset.js +0 -7
- package/dist/three/tess-asset.js.map +0 -1
- package/dist/three/tess.d.ts +0 -24
- package/dist/three/tess.d.ts.map +0 -1
- package/dist/three/tess.js +0 -24
- package/dist/three/tess.js.map +0 -1
- package/dist/three/tushar-asset.d.ts +0 -7
- package/dist/three/tushar-asset.d.ts.map +0 -1
- package/dist/three/tushar-asset.js +0 -7
- package/dist/three/tushar-asset.js.map +0 -1
- package/dist/three/tushar.d.ts +0 -22
- package/dist/three/tushar.d.ts.map +0 -1
- package/dist/three/tushar.js +0 -22
- package/dist/three/tushar.js.map +0 -1
- package/dist/types.d.ts +0 -149
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -140
- package/dist/types.js.map +0 -1
- package/dist/useAvatar.d.ts +0 -39
- package/dist/useAvatar.d.ts.map +0 -1
- package/dist/useAvatar.js +0 -35
- package/dist/useAvatar.js.map +0 -1
- package/src/avatar.d.ts +0 -436
- package/src/avatar.js +0 -1378
- package/src/behavior.d.ts +0 -42
- package/src/behavior.js +0 -114
- package/src/camera.js +0 -29
- package/src/clips.js +0 -155
- package/src/conformance.js +0 -119
- package/src/emotions.js +0 -58
- package/src/face-core.js +0 -180
- package/src/face-myna.d.ts +0 -7
- package/src/face-myna.js +0 -786
- package/src/face-peep-control-plane.js +0 -167
- package/src/face-peep.d.ts +0 -7
- package/src/face-peep.js +0 -925
- package/src/face-wren.d.ts +0 -7
- package/src/face-wren.js +0 -468
- package/src/faces.d.ts +0 -12
- package/src/faces.js +0 -53
- package/src/gaze.js +0 -504
- package/src/hand.js +0 -709
- package/src/head.js +0 -134
- package/src/idle.js +0 -668
- package/src/interjections.js +0 -747
- package/src/line-art.js +0 -137
- package/src/params.js +0 -180
- package/src/perform.js +0 -107
- package/src/prosody.js +0 -741
- package/src/rig.d.ts +0 -31
- package/src/rig.js +0 -32
- package/src/speech-timing.js +0 -51
- package/src/visemes.js +0 -410
package/src/face-wren.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { AvatarMeta, Face, FaceFactory } from "./avatar.js";
|
|
2
|
-
|
|
3
|
-
export const createFace: FaceFactory;
|
|
4
|
-
export const META: AvatarMeta;
|
|
5
|
-
/** Default palette. Passed as `createAvatar({ theme })`; see authoring-a-face.md. */
|
|
6
|
-
export const THEME: Readonly<Record<string, string>>;
|
|
7
|
-
export const wren: Face;
|
package/src/face-wren.js
DELETED
|
@@ -1,468 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Avatar: "wren".
|
|
3
|
-
*
|
|
4
|
-
* The second line-art character, and the proof run for the recipe in
|
|
5
|
-
* docs/authoring-a-face.md: static art + a POSE spec + feature blocks + META,
|
|
6
|
-
* with the stroke engine imported from line-art.js rather than carried along.
|
|
7
|
-
* Character: female, thirties, a big natural-curl mass, round glasses, a
|
|
8
|
-
* crew-neck tee. Same idiom as peep — no strokes anywhere, ink and paper and
|
|
9
|
-
* one accent — different person.
|
|
10
|
-
*
|
|
11
|
-
* What is deliberately different from peep, and why it is cheap:
|
|
12
|
-
* - THE HAIR IS THE SILHOUETTE. A scalloped cloud four times peep's hair mass,
|
|
13
|
-
* drawn as one closed loop (outer lobes out, hairline lobes back), covering
|
|
14
|
-
* the ears entirely — which is why this file has no ear geometry at all.
|
|
15
|
-
* - THE GLASSES ARE THE ACCENT. peep spends its one colour on the collar; wren
|
|
16
|
-
* wears it on the frames, which makes the two instantly tellable apart at
|
|
17
|
-
* any size where a face reads at all.
|
|
18
|
-
* - CREW NECK, NOT POLO. One band arc instead of blades + placket.
|
|
19
|
-
*
|
|
20
|
-
* What the glasses cost the rig: the eye beans live inside the lens rings, so
|
|
21
|
-
* pupil travel is capped by the frame (9/7 against peep's 11/8) — past that
|
|
22
|
-
* the bean collides with the ring and reads as the eye hitting glass. In
|
|
23
|
-
* exchange the ring is a fixed reference the bean moves against, which makes
|
|
24
|
-
* small gaze shifts MORE legible than on peep, whose beans move against
|
|
25
|
-
* nothing.
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
import { clamp, lerp } from './params.js';
|
|
29
|
-
import {
|
|
30
|
-
f, createFaceShell, faceApi, poseTransforms, pairedTeeth,
|
|
31
|
-
} from './face-core.js';
|
|
32
|
-
import { taper, taperRing, region } from './line-art.js';
|
|
33
|
-
import { viewBoxForHead } from './camera.js';
|
|
34
|
-
|
|
35
|
-
export const THEME = {
|
|
36
|
-
ink: '#1b1b1b',
|
|
37
|
-
paper: '#ffffff',
|
|
38
|
-
accent: '#f97415',
|
|
39
|
-
mouthIn: '#1b1b1b',
|
|
40
|
-
teeth: '#ffffff',
|
|
41
|
-
tongue: '#8d7f79',
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
// Wren's hair cloud is her visible crown; the skull begins well below it.
|
|
45
|
-
const FRAME = { centerX: 380, crownY: 102, chinY: 575 };
|
|
46
|
-
const VB = viewBoxForHead(FRAME);
|
|
47
|
-
|
|
48
|
-
export const META = {
|
|
49
|
-
viewBox: { x: VB.x, y: VB.y, w: VB.w, h: VB.h },
|
|
50
|
-
mouthCrop: { x: 298, y: 432, w: 164, h: 96 },
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
// --- landmarks --------------------------------------------------------------
|
|
54
|
-
const CX = FRAME.centerX;
|
|
55
|
-
const HEAD_TOP = 148;
|
|
56
|
-
const CHIN_Y = 572;
|
|
57
|
-
|
|
58
|
-
const EYE = { y: 386, dx: 55, rx: 15, ry: 17.5 };
|
|
59
|
-
const NOSE_TOP = 408;
|
|
60
|
-
const MOUTH = { cx: CX, cy: 486 };
|
|
61
|
-
const MOUTH_APERTURE = 36;
|
|
62
|
-
|
|
63
|
-
// Brows: shorter and rounder than peep's, sitting clear above the lens rings
|
|
64
|
-
// (lens top is y=355; a full browRaise lifts these 15 units and they still
|
|
65
|
-
// never touch the frames). Drawn control points, per the peep correction —
|
|
66
|
-
// the arch is not recoverable from endpoints.
|
|
67
|
-
const BROW_L = [[CX - 22, 342], [CX - 40, 334], [CX - 58, 330], [CX - 72, 331],
|
|
68
|
-
[CX - 82, 334], [CX - 89, 338], [CX - 93, 343]];
|
|
69
|
-
const BROW_R = [[CX + 24, 340], [CX + 42, 332], [CX + 60, 329], [CX + 73, 330],
|
|
70
|
-
[CX + 82, 333], [CX + 88, 337], [CX + 92, 342]];
|
|
71
|
-
|
|
72
|
-
// ---------------------------------------------------------------------------
|
|
73
|
-
// Static art: head.
|
|
74
|
-
//
|
|
75
|
-
// Same three-segment jaw rule as every head in this project (an unbroken
|
|
76
|
-
// ear-to-ear curve is an egg), but the runs are softer: the gonion corner is
|
|
77
|
-
// rounded off and the chin pad is narrower — that softness, not any single
|
|
78
|
-
// feature, is most of what makes this face read as a different person before
|
|
79
|
-
// the hair even loads. Chin a touch left of midline; symmetric heads read
|
|
80
|
-
// as machine output.
|
|
81
|
-
// ---------------------------------------------------------------------------
|
|
82
|
-
const HEAD = [
|
|
83
|
-
[CX, HEAD_TOP],
|
|
84
|
-
[452, 147], [520, 204], [524, 288],
|
|
85
|
-
[527, 340], [523, 390], [520, 428],
|
|
86
|
-
[518, 462], [513, 486], [502, 510],
|
|
87
|
-
[488, 532], [456, 558], [426, 568],
|
|
88
|
-
[408, 575], [352, 574], [334, 566],
|
|
89
|
-
[308, 554], [282, 534], [264, 508],
|
|
90
|
-
[250, 482], [244, 458], [248, 426],
|
|
91
|
-
[242, 390], [237, 340], [240, 288],
|
|
92
|
-
[244, 202], [308, 145], [CX, HEAD_TOP],
|
|
93
|
-
];
|
|
94
|
-
const HEAD_W = [3.5, 7, 11, 14, 15, 13.5, 10, 7, 4.5, 3.5];
|
|
95
|
-
|
|
96
|
-
// Neck: peep's truncated-cone construction, narrower. Fused into the head
|
|
97
|
-
// layer and run long past the neckline for the same layer-parallax reasons.
|
|
98
|
-
const NECK_FILL =
|
|
99
|
-
'M336 514C326 575 318 630 316 700L316 790L444 790L444 700C442 630 434 575 424 514Z';
|
|
100
|
-
const NECK_L = [[336, 532], [329, 588], [323, 656], [320, 750]];
|
|
101
|
-
const NECK_R = [[424, 528], [431, 584], [437, 654], [440, 750]];
|
|
102
|
-
const JAW_UNDER = [[336, 560], [354, 586], [408, 588], [428, 558]];
|
|
103
|
-
|
|
104
|
-
// ---------------------------------------------------------------------------
|
|
105
|
-
// Static art: hair — the curl cloud.
|
|
106
|
-
//
|
|
107
|
-
// One closed loop: outer silhouette left-to-right over the crown as a chain of
|
|
108
|
-
// LOBES (each segment's controls pushed outward, so consecutive lobes meet at
|
|
109
|
-
// soft cusps — that cusp rhythm is what says "curls" with zero interior
|
|
110
|
-
// marks), then the hairline right-to-left back across the forehead as smaller
|
|
111
|
-
// lobes pointing down. What the loop encloses is the hair band itself: the
|
|
112
|
-
// sides run 40 units wide down to y≈470, which is why wren needs no ears.
|
|
113
|
-
// ---------------------------------------------------------------------------
|
|
114
|
-
const HAIR = [
|
|
115
|
-
[252, 468],
|
|
116
|
-
// up the left side, three outer lobes
|
|
117
|
-
[214, 442], [206, 408], [218, 382],
|
|
118
|
-
[188, 352], [190, 306], [218, 284],
|
|
119
|
-
[196, 240], [222, 196], [262, 186],
|
|
120
|
-
// over the crown
|
|
121
|
-
[286, 138], [330, 116], [366, 124],
|
|
122
|
-
[398, 102], [446, 106], [462, 130],
|
|
123
|
-
[508, 138], [534, 170], [526, 196],
|
|
124
|
-
// down the right side
|
|
125
|
-
[560, 226], [562, 268], [546, 290],
|
|
126
|
-
[568, 322], [566, 362], [538, 388],
|
|
127
|
-
[552, 424], [540, 456], [508, 466],
|
|
128
|
-
// transition onto the hairline
|
|
129
|
-
[500, 462], [498, 458], [496, 452],
|
|
130
|
-
// back across the forehead, right to left: smaller lobes, pointing down
|
|
131
|
-
[500, 396], [498, 352], [486, 330],
|
|
132
|
-
[482, 296], [468, 278], [452, 272],
|
|
133
|
-
[430, 252], [396, 246], [380, 256],
|
|
134
|
-
[362, 242], [326, 252], [306, 270],
|
|
135
|
-
[288, 282], [278, 306], [272, 330],
|
|
136
|
-
[262, 352], [258, 400], [258, 452],
|
|
137
|
-
// close the loop at the bottom-left
|
|
138
|
-
[256, 460], [254, 464], [252, 468],
|
|
139
|
-
];
|
|
140
|
-
const HAIR_D = region(HAIR);
|
|
141
|
-
|
|
142
|
-
// ---------------------------------------------------------------------------
|
|
143
|
-
// Static art: glasses.
|
|
144
|
-
//
|
|
145
|
-
// Round frames in the ACCENT colour — wren's version of peep's collar trim.
|
|
146
|
-
// Rings are taperRings with a gently uneven profile (first and last width
|
|
147
|
-
// equal, or the ring seams at the 3 o'clock point). The temples run outward
|
|
148
|
-
// and vanish under the hair band, which is what lets them be four points long.
|
|
149
|
-
// ---------------------------------------------------------------------------
|
|
150
|
-
const LENS_R = 29;
|
|
151
|
-
const K = 0.5523; // circle-as-four-cubics constant
|
|
152
|
-
|
|
153
|
-
function circlePts(cx, cy, r) {
|
|
154
|
-
const k = r * K;
|
|
155
|
-
return [
|
|
156
|
-
[cx + r, cy], [cx + r, cy + k], [cx + k, cy + r], [cx, cy + r],
|
|
157
|
-
[cx - k, cy + r], [cx - r, cy + k], [cx - r, cy], [cx - r, cy - k],
|
|
158
|
-
[cx - k, cy - r], [cx, cy - r], [cx + k, cy - r], [cx + r, cy - k],
|
|
159
|
-
[cx + r, cy],
|
|
160
|
-
];
|
|
161
|
-
}
|
|
162
|
-
const LENS_L = circlePts(CX - EYE.dx, EYE.y, LENS_R);
|
|
163
|
-
const LENS_R_ = circlePts(CX + EYE.dx, EYE.y, LENS_R);
|
|
164
|
-
const RING_W = [5, 6, 5, 6.5, 5];
|
|
165
|
-
const BRIDGE = [[CX - 26, 378], [CX - 10, 366], [CX + 10, 366], [CX + 26, 378]];
|
|
166
|
-
const TEMPLE_L = [[CX - 86, 384], [CX - 100, 380], [CX - 112, 378], [CX - 121, 378]];
|
|
167
|
-
const TEMPLE_R = [[CX + 86, 384], [CX + 100, 380], [CX + 112, 378], [CX + 121, 378]];
|
|
168
|
-
|
|
169
|
-
/** The nose: one mark, a shorter hook than peep's — the bridge work is done
|
|
170
|
-
* by the glasses, so the nose only has to land the tip. */
|
|
171
|
-
const NOSE_D = taper(
|
|
172
|
-
[[CX - 3, NOSE_TOP], [CX - 10, 424], [CX - 15, 438], [CX - 9, 448],
|
|
173
|
-
[CX - 3, 455], [CX + 9, 453], [CX + 15, 444]],
|
|
174
|
-
[2, 8, 3]
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
// ---------------------------------------------------------------------------
|
|
178
|
-
// Static art: torso — a crew-neck tee.
|
|
179
|
-
//
|
|
180
|
-
// Same two-run shoulder construction as peep (outer arm edge, hard turn at
|
|
181
|
-
// the acromion, near-horizontal trapezius shelf), set slightly narrower. The
|
|
182
|
-
// neckline is one band arc with an ink underline — no blades, no placket.
|
|
183
|
-
// ---------------------------------------------------------------------------
|
|
184
|
-
const TORSO = [
|
|
185
|
-
[60, 950],
|
|
186
|
-
[62, 864], [104, 750], [184, 720],
|
|
187
|
-
[240, 710], [286, 676], [322, 632],
|
|
188
|
-
[334, 662], [426, 664], [440, 628],
|
|
189
|
-
[474, 672], [518, 706], [570, 716],
|
|
190
|
-
[648, 746], [694, 860], [696, 950],
|
|
191
|
-
];
|
|
192
|
-
const TORSO_W = [9, 10, 8, 7.5, 10, 9];
|
|
193
|
-
|
|
194
|
-
const SEAM_L = [[186, 722], [168, 782], [172, 862], [186, 950]];
|
|
195
|
-
const SEAM_R = [[568, 718], [587, 778], [583, 858], [572, 950]];
|
|
196
|
-
|
|
197
|
-
const NECKBAND = [[322, 630], [340, 645], [360, 655], [380, 658],
|
|
198
|
-
[400, 654], [422, 644], [438, 627]];
|
|
199
|
-
|
|
200
|
-
// Two creases, on the right this time — cloth folds where the body pulls it,
|
|
201
|
-
// and mirroring peep's left-side set would make the two shirts read as the
|
|
202
|
-
// same drawing recoloured.
|
|
203
|
-
const CREASES = [
|
|
204
|
-
{ p: [[452, 738], [447, 764], [446, 788], [450, 806]], w: [2, 6, 2] },
|
|
205
|
-
{ p: [[478, 742], [474, 766], [473, 790], [476, 808]], w: [2, 5, 2] },
|
|
206
|
-
];
|
|
207
|
-
|
|
208
|
-
// ---------------------------------------------------------------------------
|
|
209
|
-
// Layers: the same four as peep, same order, same parallax.
|
|
210
|
-
// ---------------------------------------------------------------------------
|
|
211
|
-
const PARALLAX = { head: 1.0, body: 0.1, features: 1.22, hair: 1.12 };
|
|
212
|
-
const LAYERS = ['head', 'body', 'features', 'hair'];
|
|
213
|
-
const PIVOT = { x: CX, y: 700 };
|
|
214
|
-
|
|
215
|
-
// yawPx a touch under peep's 28: the hair cloud is a much larger mass and at
|
|
216
|
-
// 28 its parallax slide against the head read as a wig shifting.
|
|
217
|
-
const POSE = {
|
|
218
|
-
leanTravel: 23, leanPivot: { x: PIVOT.x, y: 560 },
|
|
219
|
-
shrugLift: 30, shrugTiltDeg: 1.8, shrugPivot: { x: PIVOT.x, y: 800 },
|
|
220
|
-
yawPx: 26, pitchPx: 16,
|
|
221
|
-
pivot: PIVOT,
|
|
222
|
-
// peep's breath and trunk numbers; same construction, same art units, and
|
|
223
|
-
// the two characters should breathe alike. See face-peep.js for the
|
|
224
|
-
// derivation.
|
|
225
|
-
breathSwell: 0.008, swellPivot: { x: CX, y: 950 },
|
|
226
|
-
turnPx: 16,
|
|
227
|
-
layers: LAYERS, parallax: PARALLAX,
|
|
228
|
-
torsoLayers: ['body'],
|
|
229
|
-
units: 1,
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
// ---------------------------------------------------------------------------
|
|
233
|
-
// Generators: mouth. peep's contour model with wren's landmarks — the model
|
|
234
|
-
// carried over intact; only the sizing constants are this face's own. See
|
|
235
|
-
// face-peep.js for the full derivation commentary (aperture-not-centreline,
|
|
236
|
-
// the 0.18 compensation ramp, 3:1 downward opening, the cubic 0.75 solve).
|
|
237
|
-
// ---------------------------------------------------------------------------
|
|
238
|
-
function mouthGeometry(p) {
|
|
239
|
-
const cx = MOUTH.cx;
|
|
240
|
-
const cy = MOUTH.cy;
|
|
241
|
-
const open = clamp(p.mouthOpen);
|
|
242
|
-
const round = clamp(p.mouthRound);
|
|
243
|
-
const tuck = clamp(p.mouthTuck);
|
|
244
|
-
|
|
245
|
-
const w = (24 + clamp(p.mouthWidth) * 30) * (1 - 0.36 * round);
|
|
246
|
-
|
|
247
|
-
const t = 1 - 0.4 * clamp(p.mouthPress);
|
|
248
|
-
const profile = [2.5 * t, 9.5 * t, 3 * t, 10.5 * t * (1 + 0.35 * tuck), 2.5 * t];
|
|
249
|
-
const halfUp = profile[1] / 2;
|
|
250
|
-
const halfLo = profile[3] / 2;
|
|
251
|
-
|
|
252
|
-
const h = open * MOUTH_APERTURE;
|
|
253
|
-
const k = clamp(open / 0.18);
|
|
254
|
-
|
|
255
|
-
const yL = cy - 1.5 - p.mouthCornerL * 22;
|
|
256
|
-
const yR = cy - 1.5 - p.mouthCornerR * 22;
|
|
257
|
-
|
|
258
|
-
const apTop = cy - h * 0.25;
|
|
259
|
-
let apBot = cy + h * 0.75;
|
|
260
|
-
if (tuck > 0) apBot = Math.max(apTop + 6, apBot - tuck * (h * 0.6 + 4));
|
|
261
|
-
|
|
262
|
-
const cornerMid = (yL + yR) / 8;
|
|
263
|
-
const topY = (apTop - k * halfUp - cornerMid) / 0.75;
|
|
264
|
-
const botY = (apBot + k * halfLo - cornerMid) / 0.75;
|
|
265
|
-
|
|
266
|
-
const contour = [
|
|
267
|
-
[cx - w, yL],
|
|
268
|
-
[cx - w * 0.55, topY], [cx + w * 0.55, topY], [cx + w, yR],
|
|
269
|
-
[cx + w * 0.55, botY], [cx - w * 0.55, botY], [cx - w, yL],
|
|
270
|
-
];
|
|
271
|
-
|
|
272
|
-
const innerTop = cornerMid + 0.75 * topY + halfUp;
|
|
273
|
-
const innerBot = cornerMid + 0.75 * botY - halfLo;
|
|
274
|
-
|
|
275
|
-
return { contour, profile, cx, cy, w, h, topY, botY, innerTop, innerBot, open, tuck };
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/** peep's dental-arch teeth, verbatim but for the width factors. */
|
|
279
|
-
function teethPath(m, amt, lower) {
|
|
280
|
-
if (amt < 0.01) return '';
|
|
281
|
-
const gap = m.innerBot - m.innerTop;
|
|
282
|
-
if (gap < 2) return '';
|
|
283
|
-
const tw = m.w * (lower ? 0.6 : 0.76);
|
|
284
|
-
const cap = lower ? 0.5 : 0.5 + 0.35 * m.tuck;
|
|
285
|
-
const th = Math.min(amt * (lower ? 13 : 20), gap * cap);
|
|
286
|
-
|
|
287
|
-
if (lower) {
|
|
288
|
-
const base = m.innerBot + 8;
|
|
289
|
-
const edge = m.innerBot - th;
|
|
290
|
-
const end = m.innerBot - th * 0.35;
|
|
291
|
-
return (
|
|
292
|
-
`M${f(m.cx - tw)} ${f(base)}L${f(m.cx + tw)} ${f(base)}` +
|
|
293
|
-
`L${f(m.cx + tw * 0.92)} ${f(end)}` +
|
|
294
|
-
`Q${f(m.cx)} ${f(2 * edge - end)} ${f(m.cx - tw * 0.92)} ${f(end)}Z`
|
|
295
|
-
);
|
|
296
|
-
}
|
|
297
|
-
const top = m.innerTop - 8;
|
|
298
|
-
const edge = m.innerTop + th;
|
|
299
|
-
const end = m.innerTop + th * 0.35;
|
|
300
|
-
return (
|
|
301
|
-
`M${f(m.cx - tw)} ${f(top)}L${f(m.cx + tw)} ${f(top)}` +
|
|
302
|
-
`L${f(m.cx + tw * 0.92)} ${f(end)}` +
|
|
303
|
-
`Q${f(m.cx)} ${f(2 * edge - end)} ${f(m.cx - tw * 0.92)} ${f(end)}Z`
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Eyes: peep's bean model, verbatim but for the tilt values (wren's beans sit
|
|
308
|
-
// nearly level — the glasses rings already give the face its geometry, and a
|
|
309
|
-
// strong bean tilt inside a perfect circle reads as misaligned lenses).
|
|
310
|
-
function eyePath(cx, cy, lid, squint, tiltDeg) {
|
|
311
|
-
const L = clamp(lid);
|
|
312
|
-
const topY = lerp(cy - EYE.ry * 1.05, cy - EYE.ry * 0.42, L);
|
|
313
|
-
const botY = lerp(cy + EYE.ry * 1.05, cy - EYE.ry * 0.05, L) - clamp(squint) * EYE.ry * 0.7;
|
|
314
|
-
const rx = EYE.rx;
|
|
315
|
-
const a = (tiltDeg * Math.PI) / 180;
|
|
316
|
-
const ca = Math.cos(a), sa = Math.sin(a);
|
|
317
|
-
const R = (x, y) => {
|
|
318
|
-
const dx = x - cx, dy = y - cy;
|
|
319
|
-
return `${f(cx + dx * ca - dy * sa)} ${f(cy + dx * sa + dy * ca)}`;
|
|
320
|
-
};
|
|
321
|
-
return (
|
|
322
|
-
`M${R(cx - rx, cy)}` +
|
|
323
|
-
`C${R(cx - rx * 0.5, topY)} ${R(cx + rx * 0.5, topY)} ${R(cx + rx, cy)}` +
|
|
324
|
-
`C${R(cx + rx * 0.5, botY)} ${R(cx - rx * 0.5, botY)} ${R(cx - rx, cy)}Z`
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Brows: peep's point-list deformation, wren's points and a lighter profile.
|
|
329
|
-
function browPath(pts, raise, angle, inner) {
|
|
330
|
-
const n = pts.length - 1;
|
|
331
|
-
const out = pts.map(([x, y], i) => {
|
|
332
|
-
const u = i / n;
|
|
333
|
-
return [x, y - raise * 15 - inner * 11 * (1 - u) - angle * 12 * u];
|
|
334
|
-
});
|
|
335
|
-
return taper(out, [3, 12, 6], 6);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// ---------------------------------------------------------------------------
|
|
339
|
-
// Static markup
|
|
340
|
-
// ---------------------------------------------------------------------------
|
|
341
|
-
const HEAD_FILL = region(HEAD);
|
|
342
|
-
const HEAD_RING = taperRing(HEAD, HEAD_W, 10);
|
|
343
|
-
|
|
344
|
-
function markup(id, t) {
|
|
345
|
-
const ink = (d) => `<path d="${d}" fill="${t.ink}"/>`;
|
|
346
|
-
return `
|
|
347
|
-
<svg id="${id}" viewBox="${VB.x} ${VB.y} ${VB.w} ${VB.h}" xmlns="http://www.w3.org/2000/svg"
|
|
348
|
-
preserveAspectRatio="xMidYMid meet" style="display:block;width:100%;height:100%">
|
|
349
|
-
<defs><clipPath id="${id}-clipMouth"><path id="${id}-clipMouthP" d=""/></clipPath></defs>
|
|
350
|
-
|
|
351
|
-
<!-- head and neck. No ears: the hair band covers them (see the hair note).
|
|
352
|
-
Hair underlay at head parallax, same insurance as peep's — the cloud
|
|
353
|
-
abuts the skull with no margin, and the hair layer slides against the
|
|
354
|
-
head under yaw. -->
|
|
355
|
-
<g id="${id}-head">
|
|
356
|
-
<path d="${NECK_FILL}" fill="${t.paper}"/>
|
|
357
|
-
${ink(taper(NECK_L, [3, 8, 6]))}
|
|
358
|
-
${ink(taper(NECK_R, [3, 8, 6]))}
|
|
359
|
-
<path d="${HEAD_FILL}" fill="${t.paper}"/>
|
|
360
|
-
${ink(HEAD_RING)}
|
|
361
|
-
${ink(taper(JAW_UNDER, [2, 5.5, 2]))}
|
|
362
|
-
<path d="${HAIR_D}" fill="${t.ink}"/>
|
|
363
|
-
</g>
|
|
364
|
-
|
|
365
|
-
<!-- tee: silhouette, sleeve seams, neckband, creases -->
|
|
366
|
-
<g id="${id}-body">
|
|
367
|
-
<path d="${region(TORSO)}" fill="${t.paper}"/>
|
|
368
|
-
${ink(taper(TORSO, TORSO_W))}
|
|
369
|
-
${ink(taper(SEAM_L, [6, 7, 5]))}
|
|
370
|
-
${ink(taper(SEAM_R, [6, 7, 5]))}
|
|
371
|
-
${ink(taper(NECKBAND, [5, 8, 5]))}
|
|
372
|
-
${CREASES.map((c) => ink(taper(c.p, c.w))).join('\n ')}
|
|
373
|
-
</g>
|
|
374
|
-
|
|
375
|
-
<!-- features: brows, eyes, nose, glasses, mouth -->
|
|
376
|
-
<g id="${id}-features">
|
|
377
|
-
<path id="${id}-browL" fill="${t.ink}"/>
|
|
378
|
-
<path id="${id}-browR" fill="${t.ink}"/>
|
|
379
|
-
<g id="${id}-eyes">
|
|
380
|
-
<path id="${id}-eyeL" fill="${t.ink}"/>
|
|
381
|
-
<path id="${id}-eyeR" fill="${t.ink}"/>
|
|
382
|
-
</g>
|
|
383
|
-
<path d="${NOSE_D}" fill="${t.ink}"/>
|
|
384
|
-
<path d="${taperRing(LENS_L, RING_W)}" fill="${t.accent}"/>
|
|
385
|
-
<path d="${taperRing(LENS_R_, RING_W)}" fill="${t.accent}"/>
|
|
386
|
-
<path d="${taper(BRIDGE, [4, 5.5, 4])}" fill="${t.accent}"/>
|
|
387
|
-
<path d="${taper(TEMPLE_L, [4, 4.5, 5])}" fill="${t.accent}"/>
|
|
388
|
-
<path d="${taper(TEMPLE_R, [4, 4.5, 5])}" fill="${t.accent}"/>
|
|
389
|
-
<g id="${id}-mouth">
|
|
390
|
-
<path id="${id}-mouthIn" fill="${t.mouthIn}"/>
|
|
391
|
-
<g clip-path="url(#${id}-clipMouth)">
|
|
392
|
-
<ellipse id="${id}-tongue" fill="${t.tongue}"/>
|
|
393
|
-
<path id="${id}-teeth" fill="${t.teeth}"/>
|
|
394
|
-
<path id="${id}-teethLo" fill="${t.teeth}" opacity=".85"/>
|
|
395
|
-
</g>
|
|
396
|
-
<path id="${id}-lips" fill="${t.ink}"/>
|
|
397
|
-
</g>
|
|
398
|
-
</g>
|
|
399
|
-
|
|
400
|
-
<!-- hair -->
|
|
401
|
-
<g id="${id}-hair">
|
|
402
|
-
<path d="${HAIR_D}" fill="${t.ink}"/>
|
|
403
|
-
</g>
|
|
404
|
-
</svg>`;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// ---------------------------------------------------------------------------
|
|
408
|
-
// Renderer — peep's apply() shape exactly; only the travels differ.
|
|
409
|
-
// ---------------------------------------------------------------------------
|
|
410
|
-
let uid = 0;
|
|
411
|
-
|
|
412
|
-
export function createFace(mount, theme = {}) {
|
|
413
|
-
const t = Object.assign({}, THEME, theme);
|
|
414
|
-
const id = `wren${++uid}`;
|
|
415
|
-
const { svg, $, set } = createFaceShell(mount, id, markup(id, t));
|
|
416
|
-
|
|
417
|
-
const el = {
|
|
418
|
-
head: $('head'), body: $('body'), features: $('features'), hair: $('hair'),
|
|
419
|
-
browL: $('browL'), browR: $('browR'),
|
|
420
|
-
eyes: $('eyes'), eyeL: $('eyeL'), eyeR: $('eyeR'),
|
|
421
|
-
mouthIn: $('mouthIn'), lips: $('lips'), clipMouth: $('clipMouthP'),
|
|
422
|
-
teeth: $('teeth'), teethLo: $('teethLo'), tongue: $('tongue'),
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
function apply(p) {
|
|
426
|
-
poseTransforms(p, set, el, POSE);
|
|
427
|
-
|
|
428
|
-
// Eyes: bean travel capped by the lens rings — see the header. 10 is the
|
|
429
|
-
// most x the frame allows (bean edge 25 against an inner radius of 26);
|
|
430
|
-
// DISTRACTED is the state that needs every unit of it, same lesson as
|
|
431
|
-
// peep's 7→11 raise, hit from the other side.
|
|
432
|
-
set(el.eyes, 'transform', `translate(${f(p.pupilX * 10)} ${f(p.pupilY * 7)})`);
|
|
433
|
-
|
|
434
|
-
const lidFollow = Math.max(0, p.pupilY) * 0.22;
|
|
435
|
-
set(el.eyeL, 'd', eyePath(CX - EYE.dx, EYE.y + 1, p.lidL + lidFollow, p.squintL, -3));
|
|
436
|
-
set(el.eyeR, 'd', eyePath(CX + EYE.dx, EYE.y - 1, p.lidR + lidFollow, p.squintR, 3));
|
|
437
|
-
|
|
438
|
-
set(el.browL, 'd', browPath(BROW_L, p.browRaiseL, p.browAngleL, p.browInnerL));
|
|
439
|
-
set(el.browR, 'd', browPath(BROW_R, p.browRaiseR, p.browAngleR, p.browInnerR));
|
|
440
|
-
|
|
441
|
-
const m = mouthGeometry(p);
|
|
442
|
-
const contour = region(m.contour);
|
|
443
|
-
set(el.mouthIn, 'd', contour);
|
|
444
|
-
set(el.clipMouth, 'd', contour);
|
|
445
|
-
set(el.lips, 'd', taperRing(m.contour, m.profile, 12));
|
|
446
|
-
// Keyed off the drawn gap, not mouthOpen — a half-opaque interior is GREY,
|
|
447
|
-
// and grey on a two-value face reads as a rendering fault (peep's F bug).
|
|
448
|
-
set(el.mouthIn, 'opacity', f(clamp((m.innerBot - m.innerTop) / 3)));
|
|
449
|
-
|
|
450
|
-
pairedTeeth(p, set, el, teethPath, m);
|
|
451
|
-
|
|
452
|
-
const tg = clamp(p.tongue);
|
|
453
|
-
set(el.tongue, 'cx', f(m.cx));
|
|
454
|
-
set(el.tongue, 'cy', f(m.innerBot + 6 - tg * ((m.innerBot - m.innerTop) * 0.8 + 6)));
|
|
455
|
-
set(el.tongue, 'rx', f(m.w * 0.58));
|
|
456
|
-
set(el.tongue, 'ry', '8');
|
|
457
|
-
set(el.tongue, 'opacity', tg > 0.02 ? '1' : '0');
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
return faceApi(mount, svg, apply, t);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
/**
|
|
464
|
-
* This face as a **Face** record — `{ create, meta }`, the shape
|
|
465
|
-
* `createAvatar({ face })` takes. Importing it costs this drawing and nothing
|
|
466
|
-
* else; `src/faces.js` is the all-three table, for tooling.
|
|
467
|
-
*/
|
|
468
|
-
export const wren = { create: createFace, meta: META };
|
package/src/faces.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { Face } from "./avatar.js";
|
|
2
|
-
|
|
3
|
-
export const peep: Face;
|
|
4
|
-
export const wren: Face;
|
|
5
|
-
export const myna: Face;
|
|
6
|
-
|
|
7
|
-
/** The faces we ship. Not an open vocabulary — a third face is a code change. */
|
|
8
|
-
export type FaceName = "peep" | "wren" | "myna";
|
|
9
|
-
|
|
10
|
-
export const FACES: Readonly<Record<FaceName, Face>>;
|
|
11
|
-
export const FACE_NAMES: readonly FaceName[];
|
|
12
|
-
export const DEFAULT_FACE: FaceName;
|
package/src/faces.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The faces we ship, as a table — for tooling that wants every one of them.
|
|
3
|
-
*
|
|
4
|
-
* **Importing this costs all three drawings** (~2k lines of path data). That is
|
|
5
|
-
* the right trade for the pose sheet, the sweep and Studio,
|
|
6
|
-
* which exist to compare faces against each other. It is the wrong trade for an
|
|
7
|
-
* application, which renders one: those import a single face module and hand
|
|
8
|
-
* the record to `createAvatar` directly.
|
|
9
|
-
*
|
|
10
|
-
* import { peep } from '@voqalize/avatar/faces/peep';
|
|
11
|
-
* createAvatar({ mount, client, face: peep });
|
|
12
|
-
*
|
|
13
|
-
* A **Face** is `{ create, meta }` and nothing else.
|
|
14
|
-
*
|
|
15
|
-
* `create` is `createFace(mount, theme) -> { svg, apply, theme, destroy }`,
|
|
16
|
-
* callable standalone — the rig tooling drives faces with no mixer attached.
|
|
17
|
-
* That behavioural contract is the whole of what the *rig* needs: everything
|
|
18
|
-
* else — visemes, emotions, gaze, idle, clips, the mixer — works in parameter
|
|
19
|
-
* space and never learns which face it is driving.
|
|
20
|
-
*
|
|
21
|
-
* `meta` is the descriptor (viewBox, mouthCrop — see META in any face module):
|
|
22
|
-
* the things a HOST or a TOOL needs to frame a face without opening it. A face
|
|
23
|
-
* was once a bare factory, on the argument that a schema guessed from two of
|
|
24
|
-
* them would be wrong; the third settled it. Every rig needed exactly a framing
|
|
25
|
-
* rect and a mouth rect to stop the tooling hard-coding per-face tables, and
|
|
26
|
-
* nothing else — so that is all meta carries.
|
|
27
|
-
*
|
|
28
|
-
* The key is the face's name, not its rank. It used to be possible to read rank
|
|
29
|
-
* into it — the original rig was keyed `default`, which became a lie the moment
|
|
30
|
-
* it stopped being the one we ship. `DEFAULT_FACE` is the only place the choice
|
|
31
|
-
* is made, and `packages/avatar/client/createAvatar.ts` is the only place it is consulted.
|
|
32
|
-
*
|
|
33
|
-
* Two earlier rigs, `classic` and `blue-shirt`, were removed on 2026-08-06:
|
|
34
|
-
* stakeholders accepted the line-art pair and rejected both of the others, so
|
|
35
|
-
* carrying them was maintenance against art nobody wanted. What they taught the
|
|
36
|
-
* abstraction survives them — `face-core.js` exists because all three of the
|
|
37
|
-
* first rigs wrote the same apply(), and META exists because all three needed
|
|
38
|
-
* the same two rects. Their code is in git history if a lesson ever needs
|
|
39
|
-
* re-reading.
|
|
40
|
-
*/
|
|
41
|
-
|
|
42
|
-
import { peep } from './face-peep.js';
|
|
43
|
-
import { wren } from './face-wren.js';
|
|
44
|
-
import { myna } from './face-myna.js';
|
|
45
|
-
|
|
46
|
-
export { peep, wren, myna };
|
|
47
|
-
|
|
48
|
-
export const FACES = { peep, wren, myna };
|
|
49
|
-
|
|
50
|
-
export const FACE_NAMES = Object.keys(FACES);
|
|
51
|
-
|
|
52
|
-
/** The face a host gets when it does not name one. */
|
|
53
|
-
export const DEFAULT_FACE = 'peep';
|