@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
package/src/line-art.js DELETED
@@ -1,137 +0,0 @@
1
- /**
2
- * line-art — the variable-width stroke engine peep was built with.
3
- *
4
- * The construction idiom of the Open Peeps style is that NOTHING IS A STROKE:
5
- * every line is a filled outline whose width varies along its length, which is
6
- * what lets a mark swell in the middle and come to a point at the ends. A
7
- * uniform `stroke-width` with round caps is a rope with a blob at each end,
8
- * and it is the whole difference between "vector illustration" and "someone
9
- * drew this". Extracted from face-peep.js verbatim so the next line-art
10
- * character starts from the kit, not from a copy of peep.
11
- *
12
- * A curve is a flat list of points in polybezier form — [p0, c1, c2, p1, c3,
13
- * c4, p2, ...]. `widths` is a PROFILE across the whole mark, sampled at even
14
- * intervals: [3, 11, 3] means thin-fat-thin whether the curve has one Bézier
15
- * segment or six. Indexing per node instead is a trap — a one-segment curve
16
- * has only two nodes, so a third entry is never read and the mark comes out
17
- * blunt at one end.
18
- */
19
-
20
- import { f } from './face-core.js';
21
-
22
- export function toSegs(flat) {
23
- // Loud, because the failure is otherwise silent: a point count that is not
24
- // 3n+1 simply drops the trailing points and the mark comes out short with no
25
- // error anywhere.
26
- if (flat.length < 4 || (flat.length - 1) % 3 !== 0) {
27
- throw new Error(`curve needs 3n+1 points, got ${flat.length}`);
28
- }
29
- const out = [];
30
- for (let i = 0; i + 3 < flat.length; i += 3) out.push(flat.slice(i, i + 4));
31
- return out;
32
- }
33
-
34
- export const bezPt = (P, t) => {
35
- const u = 1 - t;
36
- return [0, 1].map(
37
- (i) => u * u * u * P[0][i] + 3 * u * u * t * P[1][i] + 3 * u * t * t * P[2][i] + t * t * t * P[3][i]
38
- );
39
- };
40
- export const bezTan = (P, t) => {
41
- const u = 1 - t;
42
- return [0, 1].map(
43
- (i) => 3 * (u * u * (P[1][i] - P[0][i]) + 2 * u * t * (P[2][i] - P[1][i]) + t * t * (P[3][i] - P[2][i]))
44
- );
45
- };
46
-
47
- export function widthAt(ws, s) {
48
- const u = s * (ws.length - 1);
49
- const i = Math.min(Math.floor(u), ws.length - 2);
50
- return ws[i] + (ws[i + 1] - ws[i]) * (u - i);
51
- }
52
-
53
- /** Sample a polybezier: point, unit normal, and normalized position along it. */
54
- export function walk(segs, per) {
55
- const out = [];
56
- segs.forEach((P, i) => {
57
- for (let k = i === 0 ? 0 : 1; k <= per; k++) {
58
- const t = k / per;
59
- const d = bezTan(P, t);
60
- const L = Math.hypot(d[0], d[1]) || 1;
61
- out.push({ p: bezPt(P, t), n: [-d[1] / L, d[0] / L], s: (i + t) / segs.length });
62
- }
63
- });
64
- return out;
65
- }
66
-
67
- export const polyD = (pts, cmd) =>
68
- pts.map(([x, y], i) => `${i ? 'L' : cmd}${f(x)} ${f(y)}`).join('');
69
-
70
- /** An open tapered mark: offset ±w/2 along the normal and close the polygon. */
71
- export function taper(flat, widths, per = 8) {
72
- const s = walk(toSegs(flat), per);
73
- const a = [], b = [];
74
- for (const { p, n, s: u } of s) {
75
- const h = widthAt(widths, u) / 2;
76
- a.push([p[0] + n[0] * h, p[1] + n[1] * h]);
77
- b.push([p[0] - n[0] * h, p[1] - n[1] * h]);
78
- }
79
- return polyD(a, 'M') + polyD(b.reverse(), 'L') + 'Z';
80
- }
81
-
82
- /**
83
- * A closed tapered outline — an annulus of varying width around a contour.
84
- * Two subpaths of opposite winding, so the inside is a hole under nonzero fill.
85
- */
86
- export function taperRing(flat, widths, per = 8) {
87
- const s = walk(toSegs(flat), per);
88
- const a = [], b = [];
89
- for (const { p, n, s: u } of s) {
90
- const h = widthAt(widths, u) / 2;
91
- a.push([p[0] + n[0] * h, p[1] + n[1] * h]);
92
- b.push([p[0] - n[0] * h, p[1] - n[1] * h]);
93
- }
94
- return polyD(a, 'M') + 'Z' + polyD(b.reverse(), 'M') + 'Z';
95
- }
96
-
97
- /** The plain region a curve encloses — the white "background" path of a part. */
98
- export function region(flat) {
99
- const segs = toSegs(flat);
100
- let d = `M${f(segs[0][0][0])} ${f(segs[0][0][1])}`;
101
- for (const P of segs) {
102
- d += `C${f(P[1][0])} ${f(P[1][1])} ${f(P[2][0])} ${f(P[2][1])} ${f(P[3][0])} ${f(P[3][1])}`;
103
- }
104
- return d + 'Z';
105
- }
106
-
107
- /**
108
- * On-curve points -> the polybezier form above, Catmull-Rom at the standard 1/6
109
- * tension. The house idiom for a FACE is hand-authored control points: a brow or
110
- * a lip contour is tuned a handle at a time and interpolation would fight that.
111
- * This is for marks whose geometry is easier to read as a list of places the
112
- * line goes through than as three-in-four control points — the hand's contours,
113
- * where the authoring question is "how far does the thumb clear the knuckles"
114
- * and every point is measured against another point. Curve control is worth
115
- * less there than a shape whose numbers can be argued about.
116
- */
117
- export function smooth(pts) {
118
- const out = [pts[0]];
119
- for (let i = 0; i < pts.length - 1; i++) {
120
- const p0 = pts[i - 1] || pts[i];
121
- const p1 = pts[i];
122
- const p2 = pts[i + 1];
123
- const p3 = pts[i + 2] || p2;
124
- out.push(
125
- [p1[0] + (p2[0] - p0[0]) / 6, p1[1] + (p2[1] - p0[1]) / 6],
126
- [p2[0] - (p3[0] - p1[0]) / 6, p2[1] - (p3[1] - p1[1]) / 6],
127
- p2
128
- );
129
- }
130
- return out;
131
- }
132
-
133
- /** Deterministic jitter, so a drawing is the same every load. */
134
- export function rng(seed) {
135
- let s = seed >>> 0;
136
- return () => ((s = (s * 1664525 + 1013904223) >>> 0) / 4294967296);
137
- }
package/src/params.js DELETED
@@ -1,180 +0,0 @@
1
- /**
2
- * The rig parameter space.
3
- *
4
- * Everything the face can do is a point in this ~30-dimensional space. Visemes,
5
- * emotions, gaze poses and gesture keyframes are all just named vectors here, so
6
- * blending them is plain arithmetic rather than SVG path surgery.
7
- *
8
- * Sign conventions (viewer's perspective):
9
- * headYaw + turns toward viewer's right
10
- * headPitch + chin down
11
- * headRoll + tilts toward viewer's right
12
- * pupilX + right, pupilY + down
13
- * browRaise + up, browAngle + outer end up, browInner + inner end up
14
- * mouthCornerL/R + up (smile)
15
- */
16
-
17
- import { JAW_RESPONSE_TAU_S, MOUTH_RESPONSE_TAU_S } from './speech-timing.js';
18
-
19
- export const REST = {
20
- // --- mouth -------------------------------------------------------------
21
- mouthOpen: 0.02, // vertical aperture, 0..1
22
- mouthWidth: 0.42, // 0 narrow .. 1 wide (0.42 is neutral)
23
- mouthRound: 0.1, // pucker / lip protrusion
24
- mouthPress: 0.15, // lips thinned & pressed together
25
- mouthTuck: 0.0, // lower lip drawn under upper teeth (F/V)
26
- // Availability is not a permanent smile. Warmth and encouragement are
27
- // explicit behavioral choices layered above this neutral resting shape.
28
- mouthCornerL: 0.0, // -1 frown .. +1 smile
29
- mouthCornerR: 0.0,
30
- teethUpper: 0.0, // how far the upper teeth show, 0..1
31
- tongue: 0.0, // tongue raised into the aperture, 0..1
32
- jaw: 0.0, // extra chin drop, follows mouthOpen but slower
33
-
34
- // --- eyes --------------------------------------------------------------
35
- // A fully-open lid shows an unnatural ring of sclera; real neutral eyes sit
36
- // with the upper lid already grazing the iris.
37
- lidL: 0.12, // 0 wide open .. 1 fully closed
38
- lidR: 0.12,
39
- squintL: 0.0, // lower lid raised (smile / suspicion)
40
- squintR: 0.0,
41
- pupilX: 0.0,
42
- pupilY: 0.05,
43
-
44
- // --- brows -------------------------------------------------------------
45
- browRaiseL: 0.0,
46
- browRaiseR: 0.0,
47
- browAngleL: 0.0,
48
- browAngleR: 0.0,
49
- browInnerL: 0.0, // inner-end lift, the "concern" muscle (AU1)
50
- browInnerR: 0.0,
51
-
52
- // --- head & body -------------------------------------------------------
53
- headYaw: 0.0,
54
- headPitch: 0.0,
55
- headRoll: 0.0,
56
- breath: 0.0, // driven by the idle layer, 0..1 through the cycle
57
-
58
- // --- shoulders & torso -------------------------------------------------
59
- // Shoulders are a floor-management channel as much as an affective one. The
60
- // single most legible "I would like to come in" signal a person gives is the
61
- // shoulders rising with an inbreath, and the face alone cannot say it.
62
- shoulderL: 0.0, // -1 dropped .. +1 raised
63
- shoulderR: 0.0,
64
- // Leaning in is engagement and leaning back is withdrawal, and in a webcam
65
- // frame both are read almost entirely as a change of scale.
66
- torsoLean: 0.0, // -1 back .. +1 forward
67
- // The trunk's own lateral axis, and the last body degree of freedom the rig
68
- // was missing. Before it, everything below the collar could do was rise,
69
- // fall and scale: a motion map over a 24-second listening run showed the
70
- // outer edge of the body travelling exactly zero pixels. A seated person's
71
- // trunk shifts sideways constantly — settling, re-settling, and lagging
72
- // after a head turn — and none of that was expressible.
73
- //
74
- // It carries the head's turn on a much slower time constant (below), which
75
- // is what buys follow-through for free: the mixer retargets it to headYaw
76
- // every frame and the difference in TAU does the rest.
77
- torsoTurn: 0.0, // -1 trunk toward viewer's left .. +1 right
78
- };
79
-
80
- // There are deliberately no arm or hand channels. The rig carried a full
81
- // forearm/hand chain — raise, spread, wrist rotation, splay, thumb, index — and
82
- // it was removed rather than fixed. Two reasons, both worth knowing before
83
- // anyone re-adds it. The framing is a head-and-shoulders portrait, so a hand
84
- // only exists at the bottom edge of the crop and every pose is a compromise
85
- // between "large enough to read" and "not covering the face"; and gesture is a
86
- // fraction of a percent of what this widget is for, against which the arm chain
87
- // was the single largest and most defect-prone body of geometry in the rig.
88
- // What the arms used to say — assent, deference, wanting the floor, apology —
89
- // is said by the head, brows, shoulders and torso lean instead, which is where
90
- // a viewer looking at a face on a video call is already looking.
91
-
92
- export const CHANNELS = Object.keys(REST);
93
-
94
- /**
95
- * Channel groups. Gesture clips declare which groups they own so that, say, a
96
- * nod during speech moves the head without stealing the mouth from the viseme
97
- * stream.
98
- */
99
- export const GROUPS = {
100
- mouth: [
101
- 'mouthOpen', 'mouthWidth', 'mouthRound', 'mouthPress', 'mouthTuck',
102
- 'teethUpper', 'tongue', 'jaw',
103
- ],
104
- smile: ['mouthCornerL', 'mouthCornerR'],
105
- eyes: ['lidL', 'lidR', 'squintL', 'squintR'],
106
- gaze: ['pupilX', 'pupilY'],
107
- brows: [
108
- 'browRaiseL', 'browRaiseR', 'browAngleL', 'browAngleR',
109
- 'browInnerL', 'browInnerR',
110
- ],
111
- head: ['headYaw', 'headPitch', 'headRoll'],
112
- body: ['breath', 'torsoLean', 'torsoTurn'],
113
- shoulders: ['shoulderL', 'shoulderR'],
114
- };
115
-
116
- /**
117
- * Per-channel smoothing time constants, in seconds. This is where the face gets
118
- * its sense of mass: the mouth snaps, the head drifts. It also does all the
119
- * viseme co-articulation for free — we never blend shapes explicitly, we just
120
- * retarget and let the mouth channels chase at ~40ms.
121
- */
122
- export const TAU = (() => {
123
- const t = {};
124
- for (const c of CHANNELS) t[c] = 0.09;
125
- for (const c of GROUPS.mouth) t[c] = MOUTH_RESPONSE_TAU_S;
126
- for (const c of GROUPS.smile) t[c] = 0.13;
127
- t.lidL = t.lidR = 0.018; // blinks must be crisp
128
- t.squintL = t.squintR = 0.12;
129
- t.pupilX = t.pupilY = 0.032; // saccades are ballistic and fast
130
- for (const c of GROUPS.brows) t[c] = 0.08;
131
- for (const c of GROUPS.head) t[c] = 0.16; // the head has real mass
132
- t.breath = 0.25;
133
- t.jaw = JAW_RESPONSE_TAU_S; // the jaw lags the lips slightly
134
- // The torso has more mass than the head and reads wrong when it hasn't.
135
- t.shoulderL = t.shoulderR = 0.19;
136
- t.torsoLean = 0.24;
137
- // Nearly 3x the head's, and that ratio is the whole point rather than a
138
- // taste call. The mixer feeds torsoTurn the *same* target as headYaw, so
139
- // every head turn is chased by a trunk that arrives late and settles late —
140
- // follow-through out of the smoothing mechanism the rig already had, with no
141
- // second animation system to keep in sync. Shorten this toward the head's
142
- // 0.16 and the two move as one rigid piece, which is the puppet read.
143
- t.torsoTurn = 0.44;
144
- return t;
145
- })();
146
-
147
- /**
148
- * Per-channel clamp bounds, applied after the layers mix and before smoothing.
149
- *
150
- * The head, mouth corners and brow angles are deliberately allowed past 1: they
151
- * are the channels a gesture clip adds to on top of an already-posed face, and
152
- * clipping them at 1 flattens the peak of every nod fired during an emotion.
153
- */
154
- export const RANGE = (() => {
155
- const r = {};
156
- for (const c of CHANNELS) r[c] = [0, 1];
157
- for (const c of GROUPS.head) r[c] = [-1.4, 1.4];
158
- r.mouthCornerL = r.mouthCornerR = [-1.4, 1.4];
159
- r.browAngleL = r.browAngleR = [-1.4, 1.4];
160
- r.browRaiseL = r.browRaiseR = [-1, 1];
161
- r.browInnerL = r.browInnerR = [-1, 1];
162
- r.pupilX = r.pupilY = [-1, 1];
163
- r.shoulderL = r.shoulderR = [-1, 1];
164
- r.torsoLean = [-1, 1];
165
- r.torsoTurn = [-1, 1];
166
- return r;
167
- })();
168
-
169
- export const clamp = (v, lo = 0, hi = 1) => (v < lo ? lo : v > hi ? hi : v);
170
- export const lerp = (a, b, t) => a + (b - a) * t;
171
-
172
- export function makeParams(overrides) {
173
- return Object.assign({}, REST, overrides);
174
- }
175
-
176
- /** Frame-rate independent exponential approach toward a target. */
177
- export function approach(cur, target, tau, dt) {
178
- if (tau <= 0) return target;
179
- return cur + (target - cur) * (1 - Math.exp(-dt / tau));
180
- }
package/src/perform.js DELETED
@@ -1,107 +0,0 @@
1
- /**
2
- * Perform — the composable action timeline.
3
- *
4
- * A performance is the server's choreography: timed verbs `{t, do, ...}` fired
5
- * against a clock, where every verb resolves to one of the widget's own enums —
6
- * states, emotions, gaze targets, and semantic actions. The vocabulary is deliberately
7
- * closed: the backend sequences what the rig already does well, it cannot
8
- * invent motion. That constraint is what makes the wire format assemblable by
9
- * a dialogue manager and reviewable by a human.
10
- *
11
- * The clock discipline is VisemeTrack's (visemes.js): ride the audio clock
12
- * whenever there is audio, because a gesture that drifts out of its own
13
- * sentence is worse than no gesture at all, and a timer drifts the moment the
14
- * tab is backgrounded. One deliberate difference: beat times fire *verbatim*,
15
- * with no LEAD_MS. Visemes lead the sound because phoneme sync is
16
- * frame-critical; a gesture arrives through its channels' own smoothing lag,
17
- * and any deliberate lead is authored into the times by the composer, not
18
- * imposed here.
19
- *
20
- * Seeking the audio backward does not re-fire earlier actions: verbs have side
21
- * effects, and replaying a nod is worse than missing one.
22
- */
23
-
24
- const VERBS = new Set(['state', 'emotion', 'gaze', 'action']);
25
- // `action` is addressed by id; the remaining verbs name an enum value.
26
- const ID_VERBS = new Set(['action']);
27
-
28
- /**
29
- * Shape hygiene for action arrays, in the spirit of normalizeCues: sort by
30
- * time, drop what cannot possibly fire — no finite `t`, an unknown verb, a
31
- * missing `name`/`id` — each with a console warning, never a throw.
32
- *
33
- * This checks *shape* only. Enum values are checked when the verb fires, by the dispatcher in
34
- * avatar.js — deliberately, and not just because importing the enums here
35
- * would be a dependency cycle: a track half-composed against a newer widget
36
- * should lose the verbs the widget doesn't know, not the whole performance.
37
- */
38
- export function normalizeActions(actions) {
39
- const out = [];
40
- for (const a of actions || []) {
41
- if (!a || typeof a.t !== 'number' || !Number.isFinite(a.t)) {
42
- console.warn('perform: dropped action without a time', a);
43
- continue;
44
- }
45
- if (!VERBS.has(a.do)) {
46
- console.warn(`perform: dropped unknown verb "${a && a.do}"`, a);
47
- continue;
48
- }
49
- const idVerb = ID_VERBS.has(a.do);
50
- if ((idVerb ? a.id : a.name) == null) {
51
- console.warn(`perform: dropped ${a.do} with no ${idVerb ? 'id' : 'name'}`, a);
52
- continue;
53
- }
54
- out.push(a);
55
- }
56
- return out.sort((x, y) => x.t - y.t);
57
- }
58
-
59
- /**
60
- * Schedules an action track against a clock. Sampled by the mixer once per
61
- * frame, so firing granularity is one frame — gestures cannot tell.
62
- */
63
- export class PerformTrack {
64
- constructor() {
65
- this.actions = [];
66
- this.clock = null;
67
- this.playing = false;
68
- this._idx = 0;
69
- /** @type {(a: object) => void} fired per action, in time order */
70
- this.onAction = null;
71
- /** Fired when the last action has fired — not when its effects finish. */
72
- this.onEnd = null;
73
- }
74
-
75
- /** @param {() => number} clock elapsed ms of whatever the track rides on */
76
- start(actions, clock) {
77
- this.actions = normalizeActions(actions);
78
- this.clock = clock;
79
- this._idx = 0;
80
- this.playing = true;
81
- if (!this.actions.length) this._finish();
82
- }
83
-
84
- /** Cancels future actions. Does not fire onEnd: a stopped performance did
85
- * not complete, and nothing downstream should be told it did. */
86
- stop() {
87
- this.playing = false;
88
- this.actions = [];
89
- this.clock = null;
90
- this._idx = 0;
91
- }
92
-
93
- update() {
94
- if (!this.playing || !this.clock) return;
95
- const now = this.clock();
96
- while (this._idx < this.actions.length && this.actions[this._idx].t <= now) {
97
- const a = this.actions[this._idx++];
98
- if (this.onAction) this.onAction(a);
99
- }
100
- if (this._idx >= this.actions.length) this._finish();
101
- }
102
-
103
- _finish() {
104
- this.playing = false;
105
- if (this.onEnd) this.onEnd();
106
- }
107
- }