@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.
Files changed (180) hide show
  1. package/README.md +65 -154
  2. package/dist/index.d.ts +37 -26
  3. package/dist/index.js +48 -25
  4. package/dist/pin.d.ts +2 -0
  5. package/dist/pin.js +3 -0
  6. package/dist/react.d.ts +21 -6
  7. package/dist/react.js +24 -6
  8. package/package.json +8 -67
  9. package/LICENSE-APACHE-2.0 +0 -201
  10. package/LICENSE-CC-BY-4.0 +0 -396
  11. package/assets/README.md +0 -60
  12. package/assets/tanvi.glb +0 -0
  13. package/assets/tanya.glb +0 -0
  14. package/assets/tara.glb +0 -0
  15. package/assets/tess.glb +0 -0
  16. package/assets/tushar.glb +0 -0
  17. package/client/Avatar.tsx +0 -57
  18. package/client/AvatarClient.ts +0 -818
  19. package/client/createAvatar.ts +0 -177
  20. package/client/index.ts +0 -44
  21. package/client/internal.ts +0 -132
  22. package/client/playout.ts +0 -95
  23. package/client/react.ts +0 -13
  24. package/client/supports.ts +0 -27
  25. package/client/three/assets.ts +0 -26
  26. package/client/three/budgets.ts +0 -27
  27. package/client/three/character-rig.ts +0 -1746
  28. package/client/three/createCharacter.ts +0 -109
  29. package/client/three/holds.ts +0 -69
  30. package/client/three/internal.ts +0 -55
  31. package/client/three/motion-limits.json +0 -124
  32. package/client/three/sequences.ts +0 -281
  33. package/client/three/tanvi-asset.ts +0 -6
  34. package/client/three/tanvi.ts +0 -30
  35. package/client/three/tanya-asset.ts +0 -6
  36. package/client/three/tanya.ts +0 -29
  37. package/client/three/tara-asset.ts +0 -25
  38. package/client/three/tara.ts +0 -36
  39. package/client/three/tess-asset.ts +0 -6
  40. package/client/three/tess.ts +0 -30
  41. package/client/three/tushar-asset.ts +0 -6
  42. package/client/three/tushar.ts +0 -28
  43. package/client/types.ts +0 -235
  44. package/client/useAvatar.ts +0 -74
  45. package/dist/Avatar.d.ts +0 -44
  46. package/dist/Avatar.d.ts.map +0 -1
  47. package/dist/Avatar.js +0 -7
  48. package/dist/Avatar.js.map +0 -1
  49. package/dist/AvatarClient.d.ts +0 -274
  50. package/dist/AvatarClient.d.ts.map +0 -1
  51. package/dist/AvatarClient.js +0 -712
  52. package/dist/AvatarClient.js.map +0 -1
  53. package/dist/createAvatar.d.ts +0 -135
  54. package/dist/createAvatar.d.ts.map +0 -1
  55. package/dist/createAvatar.js +0 -75
  56. package/dist/createAvatar.js.map +0 -1
  57. package/dist/index.d.ts.map +0 -1
  58. package/dist/index.js.map +0 -1
  59. package/dist/internal.d.ts +0 -34
  60. package/dist/internal.d.ts.map +0 -1
  61. package/dist/internal.js +0 -56
  62. package/dist/internal.js.map +0 -1
  63. package/dist/playout.d.ts +0 -42
  64. package/dist/playout.d.ts.map +0 -1
  65. package/dist/playout.js +0 -85
  66. package/dist/playout.js.map +0 -1
  67. package/dist/react.d.ts.map +0 -1
  68. package/dist/react.js.map +0 -1
  69. package/dist/supports.d.ts +0 -19
  70. package/dist/supports.d.ts.map +0 -1
  71. package/dist/supports.js +0 -25
  72. package/dist/supports.js.map +0 -1
  73. package/dist/three/assets.d.ts +0 -10
  74. package/dist/three/assets.d.ts.map +0 -1
  75. package/dist/three/assets.js +0 -23
  76. package/dist/three/assets.js.map +0 -1
  77. package/dist/three/budgets.d.ts +0 -17
  78. package/dist/three/budgets.d.ts.map +0 -1
  79. package/dist/three/budgets.js +0 -21
  80. package/dist/three/budgets.js.map +0 -1
  81. package/dist/three/character-rig.d.ts +0 -366
  82. package/dist/three/character-rig.d.ts.map +0 -1
  83. package/dist/three/character-rig.js +0 -1644
  84. package/dist/three/character-rig.js.map +0 -1
  85. package/dist/three/createCharacter.d.ts +0 -60
  86. package/dist/three/createCharacter.d.ts.map +0 -1
  87. package/dist/three/createCharacter.js +0 -84
  88. package/dist/three/createCharacter.js.map +0 -1
  89. package/dist/three/holds.d.ts +0 -38
  90. package/dist/three/holds.d.ts.map +0 -1
  91. package/dist/three/holds.js +0 -61
  92. package/dist/three/holds.js.map +0 -1
  93. package/dist/three/internal.d.ts +0 -52
  94. package/dist/three/internal.d.ts.map +0 -1
  95. package/dist/three/internal.js +0 -49
  96. package/dist/three/internal.js.map +0 -1
  97. package/dist/three/motion-limits.json +0 -124
  98. package/dist/three/sequences.d.ts +0 -71
  99. package/dist/three/sequences.d.ts.map +0 -1
  100. package/dist/three/sequences.js +0 -262
  101. package/dist/three/sequences.js.map +0 -1
  102. package/dist/three/tanvi-asset.d.ts +0 -7
  103. package/dist/three/tanvi-asset.d.ts.map +0 -1
  104. package/dist/three/tanvi-asset.js +0 -7
  105. package/dist/three/tanvi-asset.js.map +0 -1
  106. package/dist/three/tanvi.d.ts +0 -24
  107. package/dist/three/tanvi.d.ts.map +0 -1
  108. package/dist/three/tanvi.js +0 -24
  109. package/dist/three/tanvi.js.map +0 -1
  110. package/dist/three/tanya-asset.d.ts +0 -7
  111. package/dist/three/tanya-asset.d.ts.map +0 -1
  112. package/dist/three/tanya-asset.js +0 -7
  113. package/dist/three/tanya-asset.js.map +0 -1
  114. package/dist/three/tanya.d.ts +0 -23
  115. package/dist/three/tanya.d.ts.map +0 -1
  116. package/dist/three/tanya.js +0 -23
  117. package/dist/three/tanya.js.map +0 -1
  118. package/dist/three/tara-asset.d.ts +0 -26
  119. package/dist/three/tara-asset.d.ts.map +0 -1
  120. package/dist/three/tara-asset.js +0 -26
  121. package/dist/three/tara-asset.js.map +0 -1
  122. package/dist/three/tara.d.ts +0 -30
  123. package/dist/three/tara.d.ts.map +0 -1
  124. package/dist/three/tara.js +0 -30
  125. package/dist/three/tara.js.map +0 -1
  126. package/dist/three/tess-asset.d.ts +0 -7
  127. package/dist/three/tess-asset.d.ts.map +0 -1
  128. package/dist/three/tess-asset.js +0 -7
  129. package/dist/three/tess-asset.js.map +0 -1
  130. package/dist/three/tess.d.ts +0 -24
  131. package/dist/three/tess.d.ts.map +0 -1
  132. package/dist/three/tess.js +0 -24
  133. package/dist/three/tess.js.map +0 -1
  134. package/dist/three/tushar-asset.d.ts +0 -7
  135. package/dist/three/tushar-asset.d.ts.map +0 -1
  136. package/dist/three/tushar-asset.js +0 -7
  137. package/dist/three/tushar-asset.js.map +0 -1
  138. package/dist/three/tushar.d.ts +0 -22
  139. package/dist/three/tushar.d.ts.map +0 -1
  140. package/dist/three/tushar.js +0 -22
  141. package/dist/three/tushar.js.map +0 -1
  142. package/dist/types.d.ts +0 -149
  143. package/dist/types.d.ts.map +0 -1
  144. package/dist/types.js +0 -140
  145. package/dist/types.js.map +0 -1
  146. package/dist/useAvatar.d.ts +0 -39
  147. package/dist/useAvatar.d.ts.map +0 -1
  148. package/dist/useAvatar.js +0 -35
  149. package/dist/useAvatar.js.map +0 -1
  150. package/src/avatar.d.ts +0 -436
  151. package/src/avatar.js +0 -1378
  152. package/src/behavior.d.ts +0 -42
  153. package/src/behavior.js +0 -114
  154. package/src/camera.js +0 -29
  155. package/src/clips.js +0 -155
  156. package/src/conformance.js +0 -119
  157. package/src/emotions.js +0 -58
  158. package/src/face-core.js +0 -180
  159. package/src/face-myna.d.ts +0 -7
  160. package/src/face-myna.js +0 -786
  161. package/src/face-peep-control-plane.js +0 -167
  162. package/src/face-peep.d.ts +0 -7
  163. package/src/face-peep.js +0 -925
  164. package/src/face-wren.d.ts +0 -7
  165. package/src/face-wren.js +0 -468
  166. package/src/faces.d.ts +0 -12
  167. package/src/faces.js +0 -53
  168. package/src/gaze.js +0 -504
  169. package/src/hand.js +0 -709
  170. package/src/head.js +0 -134
  171. package/src/idle.js +0 -668
  172. package/src/interjections.js +0 -747
  173. package/src/line-art.js +0 -137
  174. package/src/params.js +0 -180
  175. package/src/perform.js +0 -107
  176. package/src/prosody.js +0 -741
  177. package/src/rig.d.ts +0 -31
  178. package/src/rig.js +0 -32
  179. package/src/speech-timing.js +0 -51
  180. package/src/visemes.js +0 -410
@@ -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';