mochi-avatar 1.0.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/BRAND.md +85 -0
- package/LICENSE.md +104 -0
- package/README.md +135 -0
- package/dist/canvas2d/avatar.d.ts +264 -0
- package/dist/canvas2d/avatar.d.ts.map +1 -0
- package/dist/canvas2d/avatar.js +811 -0
- package/dist/canvas2d/avatar.js.map +1 -0
- package/dist/canvas2d/face.d.ts +74 -0
- package/dist/canvas2d/face.d.ts.map +1 -0
- package/dist/canvas2d/face.js +298 -0
- package/dist/canvas2d/face.js.map +1 -0
- package/dist/canvas2d/paths.d.ts +13 -0
- package/dist/canvas2d/paths.d.ts.map +1 -0
- package/dist/canvas2d/paths.js +24 -0
- package/dist/canvas2d/paths.js.map +1 -0
- package/dist/characters/colourways.d.ts +78 -0
- package/dist/characters/colourways.d.ts.map +1 -0
- package/dist/characters/colourways.js +76 -0
- package/dist/characters/colourways.js.map +1 -0
- package/dist/characters/index.d.ts +8 -0
- package/dist/characters/index.d.ts.map +1 -0
- package/dist/characters/index.js +8 -0
- package/dist/characters/index.js.map +1 -0
- package/dist/characters/mochi.d.ts +19 -0
- package/dist/characters/mochi.d.ts.map +1 -0
- package/dist/characters/mochi.js +66 -0
- package/dist/characters/mochi.js.map +1 -0
- package/dist/core/colour.d.ts +25 -0
- package/dist/core/colour.d.ts.map +1 -0
- package/dist/core/colour.js +37 -0
- package/dist/core/colour.js.map +1 -0
- package/dist/core/envelope.d.ts +153 -0
- package/dist/core/envelope.d.ts.map +1 -0
- package/dist/core/envelope.js +141 -0
- package/dist/core/envelope.js.map +1 -0
- package/dist/core/geometry.d.ts +106 -0
- package/dist/core/geometry.d.ts.map +1 -0
- package/dist/core/geometry.js +157 -0
- package/dist/core/geometry.js.map +1 -0
- package/dist/core/idle.d.ts +163 -0
- package/dist/core/idle.d.ts.map +1 -0
- package/dist/core/idle.js +262 -0
- package/dist/core/idle.js.map +1 -0
- package/dist/core/layout.d.ts +142 -0
- package/dist/core/layout.d.ts.map +1 -0
- package/dist/core/layout.js +172 -0
- package/dist/core/layout.js.map +1 -0
- package/dist/core/lens.d.ts +47 -0
- package/dist/core/lens.d.ts.map +1 -0
- package/dist/core/lens.js +67 -0
- package/dist/core/lens.js.map +1 -0
- package/dist/core/looks.d.ts +77 -0
- package/dist/core/looks.d.ts.map +1 -0
- package/dist/core/looks.js +168 -0
- package/dist/core/looks.js.map +1 -0
- package/dist/core/motion.d.ts +168 -0
- package/dist/core/motion.d.ts.map +1 -0
- package/dist/core/motion.js +527 -0
- package/dist/core/motion.js.map +1 -0
- package/dist/core/mouth.d.ts +93 -0
- package/dist/core/mouth.d.ts.map +1 -0
- package/dist/core/mouth.js +92 -0
- package/dist/core/mouth.js.map +1 -0
- package/dist/core/plain.d.ts +23 -0
- package/dist/core/plain.d.ts.map +1 -0
- package/dist/core/plain.js +63 -0
- package/dist/core/plain.js.map +1 -0
- package/dist/core/spec.d.ts +156 -0
- package/dist/core/spec.d.ts.map +1 -0
- package/dist/core/spec.js +236 -0
- package/dist/core/spec.js.map +1 -0
- package/dist/core/spring.d.ts +53 -0
- package/dist/core/spring.d.ts.map +1 -0
- package/dist/core/spring.js +68 -0
- package/dist/core/spring.js.map +1 -0
- package/dist/core/vocabulary.d.ts +182 -0
- package/dist/core/vocabulary.d.ts.map +1 -0
- package/dist/core/vocabulary.js +66 -0
- package/dist/core/vocabulary.js.map +1 -0
- package/dist/element/dough-avatar.d.ts +34 -0
- package/dist/element/dough-avatar.d.ts.map +1 -0
- package/dist/element/dough-avatar.js +189 -0
- package/dist/element/dough-avatar.js.map +1 -0
- package/dist/element/index.d.ts +3 -0
- package/dist/element/index.d.ts.map +1 -0
- package/dist/element/index.js +35 -0
- package/dist/element/index.js.map +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/svg/silhouette.d.ts +97 -0
- package/dist/svg/silhouette.d.ts.map +1 -0
- package/dist/svg/silhouette.js +143 -0
- package/dist/svg/silhouette.js.map +1 -0
- package/package.json +90 -0
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A motion: the layer between idle and expression.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this format exists now, having been refused twice
|
|
5
|
+
*
|
|
6
|
+
* The argument for not writing it was that a format designed against ONE
|
|
7
|
+
* motion set comes out shaped like that set. That was right, and it made the
|
|
8
|
+
* missing input sound external when it was not: the answer is to author a
|
|
9
|
+
* second motion, not to wait for one. So there are two below, deliberately
|
|
10
|
+
* unalike — `nod` is short, one-shot and postural; `sway` loops and moves her
|
|
11
|
+
* gaze. Everything here is a thing both of them needed.
|
|
12
|
+
*
|
|
13
|
+
* What that shook out, which one clip would not have:
|
|
14
|
+
*
|
|
15
|
+
* - `loop`, because one of them does and one must not
|
|
16
|
+
* - keys with OPTIONAL channels, because `nod` never touches gaze and a
|
|
17
|
+
* format forcing it to write zeros would make every clip claim every
|
|
18
|
+
* channel, and then "unset" and "zero" would be the same thing
|
|
19
|
+
* - normalised time, because the two differ in duration by 6x and keys
|
|
20
|
+
* written in milliseconds cannot be retimed without rewriting them all
|
|
21
|
+
*
|
|
22
|
+
* ## Four channels, and the mouth is not among them
|
|
23
|
+
*
|
|
24
|
+
* The layer order is idle -> motion -> expression -> mouth, and no layer may
|
|
25
|
+
* overwrite the mouth. A motion that could write `mouthOpen` would be able to
|
|
26
|
+
* hold her jaw shut while audio is playing, which reads as broken. The
|
|
27
|
+
* channels here are POSTURAL -- squash, lean, and where she is looking -- so
|
|
28
|
+
* the rule is a property of the vocabulary rather than something each clip has
|
|
29
|
+
* to respect.
|
|
30
|
+
*/
|
|
31
|
+
import { DRIFT } from './idle.js';
|
|
32
|
+
/**
|
|
33
|
+
* Everything a motion is allowed to move. See the header.
|
|
34
|
+
*
|
|
35
|
+
* ## The three that were added, and why they are not more
|
|
36
|
+
*
|
|
37
|
+
* `squash`, `lean`, `gazeX`, `gazeY` are postural and could not express a
|
|
38
|
+
* character who goes anywhere: a hop with no vertical channel is a crouch, and
|
|
39
|
+
* a wander with no horizontal one is a lean. So:
|
|
40
|
+
*
|
|
41
|
+
* - `lift` — up, in fractions of her body HEIGHT. Positive leaves the ground.
|
|
42
|
+
* - `shift` — right, in fractions of her body WIDTH.
|
|
43
|
+
* - `turn` — -1..1, how far her features have slid toward one side.
|
|
44
|
+
*
|
|
45
|
+
* `turn` is NOT a rotation and cannot become one. One front-facing silhouette
|
|
46
|
+
* is drawn; there is no second view and no yaw. What it does is slide the
|
|
47
|
+
* features across the body, and everything is clipped to the silhouette
|
|
48
|
+
* already, so the far eye passes out of sight around her edge. That reads as
|
|
49
|
+
* turning to look at something and does not read as turning around. Anyone
|
|
50
|
+
* wanting the second thing has to draw her from behind, which is art rather
|
|
51
|
+
* than a channel.
|
|
52
|
+
*
|
|
53
|
+
* The mouth is still not among these, for the reason the header gives.
|
|
54
|
+
*
|
|
55
|
+
* ## Translation is a channel, not a window move
|
|
56
|
+
*
|
|
57
|
+
* `lift` and `shift` move her INSIDE her own window, and `paint` applies them
|
|
58
|
+
* by offsetting the origin every other coordinate is derived from -- so the
|
|
59
|
+
* outline, the features and the hit-test silhouette all travel together, and
|
|
60
|
+
* click-through keeps following her painted pixels for free.
|
|
61
|
+
*
|
|
62
|
+
* Moving the WINDOW was the alternative and is worse: `setPosition` from main
|
|
63
|
+
* on every frame of a renderer-driven animation is two processes disagreeing
|
|
64
|
+
* about where she is sixty times a second, which is the problem `drag.ts`
|
|
65
|
+
* already needs a cursor poll to solve.
|
|
66
|
+
*/
|
|
67
|
+
export const MOTION_CHANNELS = [
|
|
68
|
+
'squash',
|
|
69
|
+
'lean',
|
|
70
|
+
'gazeX',
|
|
71
|
+
'gazeY',
|
|
72
|
+
'lift',
|
|
73
|
+
'shift',
|
|
74
|
+
'turn',
|
|
75
|
+
];
|
|
76
|
+
/**
|
|
77
|
+
* How far through a clip we are, or null once a one-shot has finished.
|
|
78
|
+
*
|
|
79
|
+
* Null rather than 1: a finished one-shot contributes NOTHING, and holding its
|
|
80
|
+
* last key would leave her permanently leaning if a clip happened to end away
|
|
81
|
+
* from neutral. The caller stops mixing the layer in at all.
|
|
82
|
+
*/
|
|
83
|
+
export function progress(clip, elapsedMs) {
|
|
84
|
+
if (elapsedMs < 0)
|
|
85
|
+
return 0;
|
|
86
|
+
const raw = elapsedMs / clip.durationMs;
|
|
87
|
+
if (raw < 1)
|
|
88
|
+
return raw;
|
|
89
|
+
return clip.loop ? raw % 1 : null;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The pose at a moment, interpolated between the keys around it.
|
|
93
|
+
*
|
|
94
|
+
* Per CHANNEL, not per key. A key that omits `gazeX` is not saying "zero", it
|
|
95
|
+
* is saying nothing -- so the interpolation for that channel runs between the
|
|
96
|
+
* nearest keys that DO mention it, and a channel no key mentions is absent
|
|
97
|
+
* from the result. Blending toward an implied zero instead would make every
|
|
98
|
+
* clip fight every other layer over channels it never meant to touch.
|
|
99
|
+
*/
|
|
100
|
+
export function poseAt(clip, at) {
|
|
101
|
+
const pose = {};
|
|
102
|
+
for (const channel of MOTION_CHANNELS) {
|
|
103
|
+
const value = channelAt(clip.keys, channel, at, clip.loop);
|
|
104
|
+
if (value !== null)
|
|
105
|
+
pose[channel] = value;
|
|
106
|
+
}
|
|
107
|
+
return pose;
|
|
108
|
+
}
|
|
109
|
+
function channelAt(keys, channel, at, loop) {
|
|
110
|
+
const stated = keys.filter((key) => key[channel] !== undefined);
|
|
111
|
+
if (stated.length === 0)
|
|
112
|
+
return null;
|
|
113
|
+
const first = stated[0];
|
|
114
|
+
const last = stated[stated.length - 1];
|
|
115
|
+
if (first === undefined || last === undefined)
|
|
116
|
+
return null;
|
|
117
|
+
// HELD outside the stated range rather than fading to zero. A clip that
|
|
118
|
+
// mentions `lean` only in its second half is upright until then, not
|
|
119
|
+
// drifting toward upright from somewhere unspecified.
|
|
120
|
+
if (at <= first.t)
|
|
121
|
+
return first[channel] ?? null;
|
|
122
|
+
if (at >= last.t)
|
|
123
|
+
return last[channel] ?? null;
|
|
124
|
+
for (let i = 1; i < stated.length; i += 1) {
|
|
125
|
+
const before = stated[i - 1];
|
|
126
|
+
const after = stated[i];
|
|
127
|
+
if (before === undefined || after === undefined || after.t < at)
|
|
128
|
+
continue;
|
|
129
|
+
return hermite(stated, i, channel, at, loop);
|
|
130
|
+
}
|
|
131
|
+
return last[channel] ?? null;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* A cubic through the keys, rather than a straight line between them.
|
|
135
|
+
*
|
|
136
|
+
* ## What linear interpolation actually looks like
|
|
137
|
+
*
|
|
138
|
+
* It was `a*(1-k) + b*k`, which is exact and correct and moves like a machine.
|
|
139
|
+
* Every key is a VELOCITY DISCONTINUITY: she travels at a constant speed to the
|
|
140
|
+
* key, changes speed instantaneously, and travels at another constant speed
|
|
141
|
+
* away from it. Nothing physical does that, and the eye reads the corners
|
|
142
|
+
* immediately -- reported, in as many words, as the motions being crude.
|
|
143
|
+
*
|
|
144
|
+
* This is Catmull-Rom, expressed as a Hermite with finite-difference tangents:
|
|
145
|
+
* the speed at each key is estimated from its NEIGHBOURS, so velocity carries
|
|
146
|
+
* through a key instead of restarting at it. The curve still passes exactly
|
|
147
|
+
* through every authored value, which is what keeps a clip an authored thing
|
|
148
|
+
* rather than a suggestion.
|
|
149
|
+
*
|
|
150
|
+
* ## Why not ease each segment instead
|
|
151
|
+
*
|
|
152
|
+
* A smoothstep per segment is one line and is wrong for half the library. It
|
|
153
|
+
* forces velocity to ZERO at every key, so `sway` -- which crosses zero at its
|
|
154
|
+
* midpoint on the way from one extreme to the other -- would stop dead in the
|
|
155
|
+
* middle of a continuous lean. Easing is a property of a segment; smoothness is
|
|
156
|
+
* a property of the curve, and the complaint was about the curve.
|
|
157
|
+
*
|
|
158
|
+
* ## Time is NON-UNIFORM, so the tangents are scaled by it
|
|
159
|
+
*
|
|
160
|
+
* Keys are placed where the motion needs them, not on a grid: `hop` has eight
|
|
161
|
+
* keys with gaps from 0.12 to 0.16, and `wander` has eleven. The textbook
|
|
162
|
+
* uniform Catmull-Rom assumes even spacing and produces a visible surge either
|
|
163
|
+
* side of a short segment. Dividing each difference by the real time it spans
|
|
164
|
+
* is what makes the curve independent of where the keys happen to sit.
|
|
165
|
+
*
|
|
166
|
+
* ## Loops wrap; one-shots do not
|
|
167
|
+
*
|
|
168
|
+
* A looping clip takes its neighbours from the other end, so the seam is as
|
|
169
|
+
* smooth as everywhere else -- otherwise `sway` and `wander` would kink once
|
|
170
|
+
* per cycle, forever, which is the defect the loop-closure check exists to
|
|
171
|
+
* prevent in its cruder form. A one-shot uses a one-sided difference at its
|
|
172
|
+
* ends, so it starts and finishes without being dragged by a key that is not
|
|
173
|
+
* there.
|
|
174
|
+
*
|
|
175
|
+
* Overshoot between keys is possible and is deliberate: it is what gives a
|
|
176
|
+
* landing its weight. `squashed` clamps the body scale, so the one channel
|
|
177
|
+
* where an overshoot could be structural is already bounded.
|
|
178
|
+
*/
|
|
179
|
+
function hermite(stated, index, channel, at, loop) {
|
|
180
|
+
const n = stated.length;
|
|
181
|
+
const value = (key) => key?.[channel] ?? 0;
|
|
182
|
+
const p1 = stated[index - 1];
|
|
183
|
+
const p2 = stated[index];
|
|
184
|
+
if (p1 === undefined || p2 === undefined)
|
|
185
|
+
return value(p2 ?? p1);
|
|
186
|
+
const span = p2.t - p1.t;
|
|
187
|
+
if (span <= 0)
|
|
188
|
+
return value(p2);
|
|
189
|
+
/*
|
|
190
|
+
The neighbour outside the segment, and how far away in time it is.
|
|
191
|
+
|
|
192
|
+
For a loop that is the key at the other end, one period away — the first and
|
|
193
|
+
last keys hold the same value (the closure check enforces it), so the wrap
|
|
194
|
+
skips one of them to avoid a zero-length span that would make the tangent
|
|
195
|
+
infinite.
|
|
196
|
+
*/
|
|
197
|
+
const before = index >= 2
|
|
198
|
+
? { key: stated[index - 2], dt: p1.t - (stated[index - 2]?.t ?? 0) }
|
|
199
|
+
: loop && n >= 3
|
|
200
|
+
? { key: stated[n - 2], dt: p1.t + (1 - (stated[n - 2]?.t ?? 1)) }
|
|
201
|
+
: null;
|
|
202
|
+
const after = index + 1 < n
|
|
203
|
+
? { key: stated[index + 1], dt: (stated[index + 1]?.t ?? 1) - p2.t }
|
|
204
|
+
: loop && n >= 3
|
|
205
|
+
? { key: stated[1], dt: 1 - p2.t + (stated[1]?.t ?? 0) }
|
|
206
|
+
: null;
|
|
207
|
+
// Finite differences over real time. With no neighbour the difference is
|
|
208
|
+
// one-sided, which is the segment's own slope — an end that neither
|
|
209
|
+
// accelerates into nothing nor is pulled by a key that does not exist.
|
|
210
|
+
const m1 = before === null || before.dt <= 0
|
|
211
|
+
? (value(p2) - value(p1)) / span
|
|
212
|
+
: (value(p2) - value(before.key)) / (span + before.dt);
|
|
213
|
+
const m2 = after === null || after.dt <= 0
|
|
214
|
+
? (value(p2) - value(p1)) / span
|
|
215
|
+
: (value(after.key) - value(p1)) / (span + after.dt);
|
|
216
|
+
const u = (at - p1.t) / span;
|
|
217
|
+
const uu = u * u;
|
|
218
|
+
const uuu = uu * u;
|
|
219
|
+
return ((2 * uuu - 3 * uu + 1) * value(p1) +
|
|
220
|
+
(uuu - 2 * uu + u) * span * m1 +
|
|
221
|
+
(-2 * uuu + 3 * uu) * value(p2) +
|
|
222
|
+
(uuu - uu) * span * m2);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* A short agreement, and a long one that repeats.
|
|
226
|
+
*
|
|
227
|
+
* Two, and unalike on purpose -- see the header. They are also the only
|
|
228
|
+
* motions this app has, which is why `caps.supportsMotions` can finally be
|
|
229
|
+
* true: a backend claiming the capability with an empty library would be the
|
|
230
|
+
* flag lying, and `playMotion` would report success over a face that never
|
|
231
|
+
* moved.
|
|
232
|
+
*/
|
|
233
|
+
export const BUILT_IN_MOTIONS = {
|
|
234
|
+
// Down, past centre, and settle. The overshoot is what makes it read as a
|
|
235
|
+
// nod rather than as a lurch -- the same reason `spring.ts` is second order.
|
|
236
|
+
nod: {
|
|
237
|
+
durationMs: 620,
|
|
238
|
+
loop: false,
|
|
239
|
+
keys: [
|
|
240
|
+
{ t: 0, squash: 0, lean: 0 },
|
|
241
|
+
{ t: 0.28, squash: 0.1, lean: 0.05 },
|
|
242
|
+
{ t: 0.62, squash: -0.04, lean: -0.02 },
|
|
243
|
+
{ t: 1, squash: 0, lean: 0 },
|
|
244
|
+
],
|
|
245
|
+
},
|
|
246
|
+
// A slow lean with her gaze trailing it, repeating. Nothing about her body
|
|
247
|
+
// returns to centre at the ends, because a looping clip whose first and last
|
|
248
|
+
// keys differ jumps every cycle.
|
|
249
|
+
sway: {
|
|
250
|
+
durationMs: 3800,
|
|
251
|
+
loop: true,
|
|
252
|
+
keys: [
|
|
253
|
+
{ t: 0, lean: 0, gazeX: 0 },
|
|
254
|
+
{ t: 0.25, lean: 0.06, gazeX: 0.18 },
|
|
255
|
+
{ t: 0.5, lean: 0, gazeX: 0 },
|
|
256
|
+
{ t: 0.75, lean: -0.06, gazeX: -0.18 },
|
|
257
|
+
{ t: 1, lean: 0, gazeX: 0 },
|
|
258
|
+
],
|
|
259
|
+
},
|
|
260
|
+
/**
|
|
261
|
+
* A small hop: crouch, launch, hang, land, settle.
|
|
262
|
+
*
|
|
263
|
+
* The ANTICIPATION is what makes it read as a jump rather than a twitch --
|
|
264
|
+
* she compresses before she leaves the ground, and lands compressed again.
|
|
265
|
+
* Squash and lift are deliberately out of phase: at the apex she is stretched
|
|
266
|
+
* (negative squash) and highest, and the two return to zero at different
|
|
267
|
+
* times so the landing has weight.
|
|
268
|
+
*
|
|
269
|
+
* `lift: 0.16` is about eleven pixels at her default size. Small on purpose
|
|
270
|
+
* -- this is a gesture, not a jump, and the room for it has to be reserved in
|
|
271
|
+
* her window before she can use it.
|
|
272
|
+
*/
|
|
273
|
+
hop: {
|
|
274
|
+
// Quick and SHALLOW. This is a gesture, not a jump: she leaves the ground
|
|
275
|
+
// by about eight pixels at her drawn size, which is enough to read as a
|
|
276
|
+
// hop and not enough to read as her going anywhere.
|
|
277
|
+
durationMs: 460,
|
|
278
|
+
loop: false,
|
|
279
|
+
keys: [
|
|
280
|
+
{ t: 0, squash: 0, lift: 0 },
|
|
281
|
+
// The crouch is FAST and the launch is faster: most of the clip is air.
|
|
282
|
+
{ t: 0.14, squash: 0.1, lift: 0 },
|
|
283
|
+
{ t: 0.26, squash: -0.05, lift: 0.045 },
|
|
284
|
+
{ t: 0.44, squash: -0.02, lift: 0.085 },
|
|
285
|
+
{ t: 0.62, squash: 0.015, lift: 0.042 },
|
|
286
|
+
{ t: 0.72, squash: 0.085, lift: 0 },
|
|
287
|
+
{ t: 0.86, squash: -0.025, lift: 0 },
|
|
288
|
+
{ t: 1, squash: 0, lift: 0 },
|
|
289
|
+
],
|
|
290
|
+
},
|
|
291
|
+
/**
|
|
292
|
+
* A pendulum: her body goes where it leans, repeating.
|
|
293
|
+
*
|
|
294
|
+
* The difference from `sway`, which is worth saying because they are easy to
|
|
295
|
+
* confuse: `sway` moves her GAZE and leaves her body where it is, and reads
|
|
296
|
+
* as attention wandering. This moves her body and leaves her gaze alone, and
|
|
297
|
+
* reads as contentment. Lean and shift are IN PHASE -- a pendulum travels the
|
|
298
|
+
* way it tilts, and putting them out of phase produces a body sliding out
|
|
299
|
+
* from under its own head.
|
|
300
|
+
*/
|
|
301
|
+
swing: {
|
|
302
|
+
// Slow and shallow. A pendulum you notice is a pendulum you watch; this is
|
|
303
|
+
// meant to be seen out of the corner of an eye, so the travel is about four
|
|
304
|
+
// pixels either way on her drawn body.
|
|
305
|
+
durationMs: 3400,
|
|
306
|
+
loop: true,
|
|
307
|
+
keys: [
|
|
308
|
+
{ t: 0, lean: 0, shift: 0 },
|
|
309
|
+
{ t: 0.25, lean: 0.032, shift: 0.045 },
|
|
310
|
+
{ t: 0.5, lean: 0, shift: 0 },
|
|
311
|
+
{ t: 0.75, lean: -0.032, shift: -0.045 },
|
|
312
|
+
{ t: 1, lean: 0, shift: 0 },
|
|
313
|
+
],
|
|
314
|
+
},
|
|
315
|
+
/**
|
|
316
|
+
* Turning to look at something beside her, and coming back.
|
|
317
|
+
*
|
|
318
|
+
* Not a rotation -- see the note on `MOTION_CHANNELS`. The features slide
|
|
319
|
+
* toward one edge and the silhouette clips the far one, which is what a flat
|
|
320
|
+
* character turning looks like. The lean and the gaze go with it because a
|
|
321
|
+
* head that turns without the body reads as a glance, and a glance is what
|
|
322
|
+
* the gaze channel alone already does.
|
|
323
|
+
*
|
|
324
|
+
* One-shot, and it returns to zero: a turn that stayed turned would leave her
|
|
325
|
+
* facing away for the rest of the session, and there is no view of her from
|
|
326
|
+
* that angle to make it worth looking at.
|
|
327
|
+
*/
|
|
328
|
+
turn: {
|
|
329
|
+
// A head turn is quick and the RETURN is slower than the going, which is
|
|
330
|
+
// what makes it read as looking at something rather than as scanning.
|
|
331
|
+
durationMs: 950,
|
|
332
|
+
loop: false,
|
|
333
|
+
keys: [
|
|
334
|
+
// Her face LEADS and TRAILS her body, which is how a head turn actually
|
|
335
|
+
// goes: the features start moving before the shoulders commit, and they
|
|
336
|
+
// are the last thing to come back. It also leaves a moment near the end
|
|
337
|
+
// where the lean is exactly zero and the turn is not, which is the only
|
|
338
|
+
// instant at which this channel can be measured on its own -- a clip
|
|
339
|
+
// whose channels all move together can only ever be tested as a whole.
|
|
340
|
+
{ t: 0, turn: 0, lean: 0, gazeX: 0 },
|
|
341
|
+
{ t: 0.12, turn: 0.22, lean: 0.012, gazeX: 0.12 },
|
|
342
|
+
{ t: 0.26, turn: 0.4, lean: 0.03, gazeX: 0.26 },
|
|
343
|
+
{ t: 0.58, turn: 0.39, lean: 0.034, gazeX: 0.28 },
|
|
344
|
+
{ t: 0.82, turn: 0.24, lean: 0, gazeX: 0 },
|
|
345
|
+
{ t: 1, turn: 0, lean: 0, gazeX: 0 },
|
|
346
|
+
],
|
|
347
|
+
},
|
|
348
|
+
/**
|
|
349
|
+
* Wandering: a slow drift one way, a pause, and back.
|
|
350
|
+
*
|
|
351
|
+
* LONG and slow on purpose. Everything else in this app is built so she can
|
|
352
|
+
* be ignored -- click-through by default, a halo rather than a spinner -- and
|
|
353
|
+
* a companion who crosses your screen quickly is one you have to attend to.
|
|
354
|
+
* Eleven seconds for less than half a body width each way is movement you
|
|
355
|
+
* notice only if you look.
|
|
356
|
+
*
|
|
357
|
+
* The bob is on `lift` at twice the drift's frequency, so her step and her
|
|
358
|
+
* travel are not the same beat. Both return to zero at `t: 1`, which the
|
|
359
|
+
* loop-closure check enforces for every channel rather than for the one the
|
|
360
|
+
* author was thinking about.
|
|
361
|
+
*
|
|
362
|
+
* She leans INTO the direction of travel at the start of each leg and out of
|
|
363
|
+
* it at the end, which is what a body does when it starts and stops.
|
|
364
|
+
*/
|
|
365
|
+
wander: {
|
|
366
|
+
durationMs: 9_000,
|
|
367
|
+
loop: true,
|
|
368
|
+
keys: [
|
|
369
|
+
{ t: 0, shift: 0, lean: 0, lift: 0 },
|
|
370
|
+
{ t: 0.1, shift: 0.03, lean: 0.018, lift: 0.006 },
|
|
371
|
+
{ t: 0.2, shift: 0.065, lean: 0.012, lift: 0 },
|
|
372
|
+
{ t: 0.3, shift: 0.085, lean: -0.01, lift: 0.006 },
|
|
373
|
+
{ t: 0.38, shift: 0.09, lean: 0, lift: 0 },
|
|
374
|
+
{ t: 0.5, shift: 0.072, lean: -0.018, lift: 0.006 },
|
|
375
|
+
{ t: 0.62, shift: 0, lean: -0.018, lift: 0 },
|
|
376
|
+
{ t: 0.74, shift: -0.065, lean: -0.012, lift: 0.006 },
|
|
377
|
+
{ t: 0.84, shift: -0.078, lean: 0.014, lift: 0 },
|
|
378
|
+
{ t: 0.94, shift: -0.03, lean: 0.018, lift: 0.005 },
|
|
379
|
+
{ t: 1, shift: 0, lean: 0, lift: 0 },
|
|
380
|
+
],
|
|
381
|
+
},
|
|
382
|
+
};
|
|
383
|
+
/**
|
|
384
|
+
* How far a clip takes her from where she stands, as fractions of her body.
|
|
385
|
+
*
|
|
386
|
+
* The room for a translation has to exist in her window BEFORE she uses it, or
|
|
387
|
+
* she walks into the edge of a transparent rectangle and is clipped by it. This
|
|
388
|
+
* is what `face.ts` reserves against, and it is derived from the keys rather
|
|
389
|
+
* than declared beside them: a number an author has to keep in step with the
|
|
390
|
+
* clip is a number that goes stale the first time somebody retimes it.
|
|
391
|
+
*
|
|
392
|
+
* `up` only, for `lift`. She hops; she does not sink through the floor, and
|
|
393
|
+
* reserving room below her would push her down inside her own window for a
|
|
394
|
+
* clearance nothing uses.
|
|
395
|
+
*/
|
|
396
|
+
export function motionReach(clip) {
|
|
397
|
+
let x = 0;
|
|
398
|
+
let up = 0;
|
|
399
|
+
for (const key of clip.keys) {
|
|
400
|
+
x = Math.max(x, Math.abs(key.shift ?? 0));
|
|
401
|
+
up = Math.max(up, key.lift ?? 0);
|
|
402
|
+
}
|
|
403
|
+
return { x, up };
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* The worst case across every built-in, PLUS the drift, which is what a window
|
|
407
|
+
* has to hold.
|
|
408
|
+
*
|
|
409
|
+
* The drift is continuous and additive -- she is always a little off her spot,
|
|
410
|
+
* and a clip starts from wherever that is rather than from centre. So the two
|
|
411
|
+
* reaches SUM: reserving only the clip's would clip her by a couple of pixels
|
|
412
|
+
* at whichever extreme the two happened to agree on, intermittently, which is
|
|
413
|
+
* the least debuggable kind of wrong.
|
|
414
|
+
*/
|
|
415
|
+
export function builtInReach() {
|
|
416
|
+
let x = 0;
|
|
417
|
+
let up = 0;
|
|
418
|
+
for (const clip of Object.values(BUILT_IN_MOTIONS)) {
|
|
419
|
+
const reach = motionReach(clip);
|
|
420
|
+
x = Math.max(x, reach.x);
|
|
421
|
+
up = Math.max(up, reach.up);
|
|
422
|
+
}
|
|
423
|
+
return { x: x + DRIFT.shift, up: up + DRIFT.lift };
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Turn something off disk into a clip, or say what is wrong with all of it.
|
|
427
|
+
*
|
|
428
|
+
* A package may carry motions, so this is a trust boundary and takes the same
|
|
429
|
+
* treatment `parseFaceSpec` gets: every problem reported at once
|
|
430
|
+
* rather than the first, unknown keys refused rather than dropped, and every
|
|
431
|
+
* number range-checked -- `t: 5` and `squash: 400` are both valid JSON numbers
|
|
432
|
+
* that throw nothing and produce a mochi somewhere off the screen.
|
|
433
|
+
*
|
|
434
|
+
* The ordering and loop-closure rules are checked HERE rather than trusted,
|
|
435
|
+
* because `poseAt` is written against them: keys out of order make the
|
|
436
|
+
* interpolation pick the wrong pair, and a loop whose ends differ jumps once
|
|
437
|
+
* per cycle for as long as it plays.
|
|
438
|
+
*/
|
|
439
|
+
export function parseMotionClip(value) {
|
|
440
|
+
const problems = [];
|
|
441
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
442
|
+
return { ok: false, problems: ['a motion must be a JSON object'] };
|
|
443
|
+
}
|
|
444
|
+
const source = value;
|
|
445
|
+
const durationMs = source['durationMs'];
|
|
446
|
+
if (typeof durationMs !== 'number' || !Number.isFinite(durationMs) || durationMs <= 0) {
|
|
447
|
+
problems.push('durationMs must be a positive number of milliseconds');
|
|
448
|
+
}
|
|
449
|
+
const loop = source['loop'];
|
|
450
|
+
if (typeof loop !== 'boolean')
|
|
451
|
+
problems.push('loop must be true or false');
|
|
452
|
+
for (const key of Object.keys(source)) {
|
|
453
|
+
if (!['durationMs', 'loop', 'keys'].includes(key)) {
|
|
454
|
+
problems.push(`${key} is not a field of a motion`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const rawKeys = source['keys'];
|
|
458
|
+
if (!Array.isArray(rawKeys)) {
|
|
459
|
+
problems.push('keys must be a list');
|
|
460
|
+
return { ok: false, problems };
|
|
461
|
+
}
|
|
462
|
+
// TWO, not one. A single key is a pose rather than a motion, and it would
|
|
463
|
+
// interpolate against nothing -- which reads as the clip having failed.
|
|
464
|
+
if (rawKeys.length < 2)
|
|
465
|
+
problems.push('a motion needs at least two keys');
|
|
466
|
+
const keys = [];
|
|
467
|
+
for (const [index, raw] of rawKeys.entries()) {
|
|
468
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
469
|
+
problems.push(`keys[${String(index)}] must be an object`);
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
const entry = raw;
|
|
473
|
+
const t = entry['t'];
|
|
474
|
+
if (typeof t !== 'number' || !Number.isFinite(t) || t < 0 || t > 1) {
|
|
475
|
+
problems.push(`keys[${String(index)}].t must be between 0 and 1`);
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
const key = { t };
|
|
479
|
+
for (const [name, channelValue] of Object.entries(entry)) {
|
|
480
|
+
if (name === 't')
|
|
481
|
+
continue;
|
|
482
|
+
if (!MOTION_CHANNELS.includes(name)) {
|
|
483
|
+
// Refused, not dropped: `squahs` silently ignored shows its author a
|
|
484
|
+
// motion that plays and does nothing they asked for.
|
|
485
|
+
problems.push(`keys[${String(index)}].${name} is not a channel a motion may move`);
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
if (typeof channelValue !== 'number' || !Number.isFinite(channelValue)) {
|
|
489
|
+
problems.push(`keys[${String(index)}].${name} must be a finite number`);
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
if (channelValue < -1 || channelValue > 1) {
|
|
493
|
+
// Bounded because these feed body scale and gaze. 400 is a legal
|
|
494
|
+
// number that draws a mochi nobody can see.
|
|
495
|
+
problems.push(`keys[${String(index)}].${name} must be between -1 and 1`);
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
key[name] = channelValue;
|
|
499
|
+
}
|
|
500
|
+
keys.push(key);
|
|
501
|
+
}
|
|
502
|
+
for (let i = 1; i < keys.length; i += 1) {
|
|
503
|
+
if ((keys[i]?.t ?? 0) < (keys[i - 1]?.t ?? 0)) {
|
|
504
|
+
problems.push('keys must be in order of t');
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (loop === true && keys.length >= 2) {
|
|
509
|
+
const first = keys[0];
|
|
510
|
+
const last = keys[keys.length - 1];
|
|
511
|
+
if (first !== undefined && last !== undefined) {
|
|
512
|
+
for (const channel of MOTION_CHANNELS) {
|
|
513
|
+
if ((first[channel] ?? 0) !== (last[channel] ?? 0)) {
|
|
514
|
+
// A looping clip whose ends differ jumps once per cycle, forever.
|
|
515
|
+
problems.push(`a looping motion must end where it began (${channel} differs)`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (problems.length > 0)
|
|
521
|
+
return { ok: false, problems };
|
|
522
|
+
return {
|
|
523
|
+
ok: true,
|
|
524
|
+
clip: { durationMs: durationMs, loop: loop, keys },
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
//# sourceMappingURL=motion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"motion.js","sourceRoot":"","sources":["../../src/core/motion.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single writer to her mouth.
|
|
3
|
+
*
|
|
4
|
+
* One object owns `setMouthOpen`, and that is the whole point rather than a
|
|
5
|
+
* tidiness preference. The mouth is layer 4 and is written last, so nothing
|
|
6
|
+
* above it can arbitrate between two sources -- two writers produce a mouth
|
|
7
|
+
* flickering between them at whatever rate they happen to disagree, and no
|
|
8
|
+
* layer ordering can fix it.
|
|
9
|
+
*
|
|
10
|
+
* ## Where the viseme path goes
|
|
11
|
+
*
|
|
12
|
+
* The mouth has two paths and they are not a ladder: cloud speech-to-speech
|
|
13
|
+
* carries no phoneme timings, so the envelope is the only thing available
|
|
14
|
+
* there, not a fallback. The five-vowel path belongs to a local TTS front end,
|
|
15
|
+
* whose G2P stage produces timings for free.
|
|
16
|
+
*
|
|
17
|
+
* There is deliberately no second implementation and no factory to choose
|
|
18
|
+
* between them YET. Nothing emits phoneme timings on the current road and
|
|
19
|
+
* `MochiAvatar.caps.visemes` is false, so a `VisemeMouth` would be an
|
|
20
|
+
* abstraction over one caller and a capability nobody can satisfy. When a
|
|
21
|
+
* provider reports `phonemeTimings`, it arrives as a second class behind this
|
|
22
|
+
* same interface and the choice is made ONCE -- by picking an object, never by
|
|
23
|
+
* a flag read inside a callback, which would be a branch in every frame and a
|
|
24
|
+
* place for the wrong path to be taken.
|
|
25
|
+
*
|
|
26
|
+
* ## The gate is THREE conditions, not one
|
|
27
|
+
*
|
|
28
|
+
* When that day comes, the precise path requires all of:
|
|
29
|
+
*
|
|
30
|
+
* 1. the provider emits phoneme timings,
|
|
31
|
+
* 2. the backend can render visemes (`caps.visemes`), and
|
|
32
|
+
* 3. THE LOADED MODEL carries every one of the five vowel presets.
|
|
33
|
+
*
|
|
34
|
+
* The third is the one that gets dropped, and it is the expensive one. VRM's
|
|
35
|
+
* vowel presets are optional per model, so a capable backend can load a model
|
|
36
|
+
* carrying none: routing on caps alone sends weights nowhere and leaves the
|
|
37
|
+
* mouth shut mid-word, intermittently, with every flag reporting success. RMS
|
|
38
|
+
* over the whole utterance is visibly better than four fifths of a mouth.
|
|
39
|
+
*
|
|
40
|
+
* mochi locked all three into `canUseVisemes()` in shared/avatar.ts. It is not
|
|
41
|
+
* ported here because there is nothing yet to call it -- port it together with
|
|
42
|
+
* the second driver, not before.
|
|
43
|
+
*/
|
|
44
|
+
import type { AvatarBackend } from './vocabulary.js';
|
|
45
|
+
import { type EnvelopeSettings } from './envelope.js';
|
|
46
|
+
/** Only the part of the backend a mouth driver is allowed to touch. */
|
|
47
|
+
export type MouthSink = Pick<AvatarBackend, 'setMouthOpen'>;
|
|
48
|
+
export interface MouthDriver {
|
|
49
|
+
/**
|
|
50
|
+
* The loudness of what is sounding right now, and how long since the last
|
|
51
|
+
* call. Driven from the render loop, after every other layer.
|
|
52
|
+
*/
|
|
53
|
+
observe(level: number, dtSeconds: number): void;
|
|
54
|
+
/** The turn ended, or she was interrupted. Shuts the mouth immediately. */
|
|
55
|
+
end(): void;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The RMS path.
|
|
59
|
+
*
|
|
60
|
+
* Driven once per rendered frame rather than per audio callback. Under a peer
|
|
61
|
+
* connection her voice is a remote MediaStream the browser plays itself -- no
|
|
62
|
+
* PCM reaches this process at all -- so the level arrives by sampling an
|
|
63
|
+
* analyser, which is naturally a per-frame operation. At 60Hz that is a 16ms
|
|
64
|
+
* sampling interval against a ~4Hz syllable rate, which is ample.
|
|
65
|
+
*/
|
|
66
|
+
export declare class EnvelopeMouth implements MouthDriver {
|
|
67
|
+
private readonly avatar;
|
|
68
|
+
private readonly settings;
|
|
69
|
+
private state;
|
|
70
|
+
constructor(avatar: MouthSink, settings?: EnvelopeSettings);
|
|
71
|
+
observe(level: number, dtSeconds: number): void;
|
|
72
|
+
/**
|
|
73
|
+
* Whether the envelope currently judges the signal to be speech.
|
|
74
|
+
*
|
|
75
|
+
* The one place anything outside the rig should ask that question. Reading it
|
|
76
|
+
* here rather than re-deriving it from a level means the mouth and the
|
|
77
|
+
* "is she still talking" decision can never disagree -- and they did, when
|
|
78
|
+
* the caller compared a raw RMS against a constant while this compared a peak
|
|
79
|
+
* against a learned floor.
|
|
80
|
+
*/
|
|
81
|
+
get speaking(): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Shut, and forget the level -- but KEEP the learned floor and peak.
|
|
84
|
+
*
|
|
85
|
+
* The references are what make the envelope level-independent, and they are
|
|
86
|
+
* properties of this voice on this connection, not of this turn. Discarding
|
|
87
|
+
* them at every turn boundary would make her first syllable back a
|
|
88
|
+
* recalibration, which is visible: the mouth either slams or sits closed
|
|
89
|
+
* while the estimate catches up.
|
|
90
|
+
*/
|
|
91
|
+
end(): void;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=mouth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mouth.d.ts","sourceRoot":"","sources":["../../src/core/mouth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AACpD,OAAO,EAA6C,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAEhG,uEAAuE;AACvE,MAAM,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,EAAE,cAAc,CAAC,CAAA;AAE3D,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/C,2EAA2E;IAC3E,GAAG,IAAI,IAAI,CAAA;CACZ;AAED;;;;;;;;GAQG;AACH,qBAAa,aAAc,YAAW,WAAW;IAI7C,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAJ3B,OAAO,CAAC,KAAK,CAAS;IAEtB,YACmB,MAAM,EAAE,SAAS,EACjB,QAAQ,GAAE,gBAAmC,EAC5D;IAEJ,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAG9C;IAED;;;;;;;;OAQG;IACH,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED;;;;;;;;OAQG;IACH,GAAG,IAAI,IAAI,CAGV;CACF"}
|