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,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How each emotion deforms the neutral face.
|
|
3
|
+
*
|
|
4
|
+
* Every Emotion gets an entry, enforced by `Record<Emotion, Look>`. Letting one
|
|
5
|
+
* fall through to neutral would make `caps.presetExpressions` a lie: the
|
|
6
|
+
* backend would claim to support an expression it renders identically to no
|
|
7
|
+
* expression at all, and the caller has no way to discover that.
|
|
8
|
+
*
|
|
9
|
+
* Values are multipliers on the tuned base geometry rather than absolute sizes,
|
|
10
|
+
* so retuning the face in the tuner does not invalidate the table.
|
|
11
|
+
*/
|
|
12
|
+
import { clamp01 } from './vocabulary.js';
|
|
13
|
+
export const NEUTRAL = {
|
|
14
|
+
eyeUpper: 1,
|
|
15
|
+
eyeLower: 1,
|
|
16
|
+
eyeWidth: 1,
|
|
17
|
+
eyeTilt: 0,
|
|
18
|
+
mouthUpper: 1,
|
|
19
|
+
mouthLower: 1,
|
|
20
|
+
mouthWidth: 1,
|
|
21
|
+
mouthAlpha: 1,
|
|
22
|
+
cheek: 0,
|
|
23
|
+
sparkle: 0,
|
|
24
|
+
gazeX: 0,
|
|
25
|
+
gazeY: 0,
|
|
26
|
+
squash: 0,
|
|
27
|
+
lean: 0,
|
|
28
|
+
};
|
|
29
|
+
export const LOOKS = {
|
|
30
|
+
neutral: NEUTRAL,
|
|
31
|
+
happy: {
|
|
32
|
+
...NEUTRAL,
|
|
33
|
+
eyeUpper: 1.1,
|
|
34
|
+
// Negative: the bottom edge lifts past the baseline and the eye becomes ^.
|
|
35
|
+
eyeLower: -0.62,
|
|
36
|
+
eyeWidth: 1.06,
|
|
37
|
+
mouthUpper: 0,
|
|
38
|
+
mouthLower: 2.8,
|
|
39
|
+
mouthWidth: 1.35,
|
|
40
|
+
cheek: 0.6,
|
|
41
|
+
sparkle: 1,
|
|
42
|
+
squash: 0.13,
|
|
43
|
+
},
|
|
44
|
+
shy: {
|
|
45
|
+
...NEUTRAL,
|
|
46
|
+
eyeUpper: 0.72,
|
|
47
|
+
eyeLower: -0.28,
|
|
48
|
+
eyeWidth: 0.96,
|
|
49
|
+
mouthUpper: 0,
|
|
50
|
+
mouthLower: 0.7,
|
|
51
|
+
mouthWidth: 0.66,
|
|
52
|
+
cheek: 1,
|
|
53
|
+
gazeX: -0.38,
|
|
54
|
+
gazeY: 0.28,
|
|
55
|
+
squash: 0.07,
|
|
56
|
+
lean: -0.06,
|
|
57
|
+
},
|
|
58
|
+
sad: {
|
|
59
|
+
...NEUTRAL,
|
|
60
|
+
eyeUpper: 0.82,
|
|
61
|
+
eyeLower: 1.14,
|
|
62
|
+
eyeTilt: -0.17,
|
|
63
|
+
mouthUpper: 3,
|
|
64
|
+
mouthLower: -0.3,
|
|
65
|
+
mouthWidth: 0.9,
|
|
66
|
+
gazeY: 0.34,
|
|
67
|
+
squash: -0.11,
|
|
68
|
+
},
|
|
69
|
+
angry: {
|
|
70
|
+
...NEUTRAL,
|
|
71
|
+
eyeUpper: 0.55,
|
|
72
|
+
eyeLower: 0.92,
|
|
73
|
+
eyeWidth: 1.06,
|
|
74
|
+
eyeTilt: 0.28,
|
|
75
|
+
mouthUpper: 2,
|
|
76
|
+
mouthLower: 0,
|
|
77
|
+
mouthWidth: 0.84,
|
|
78
|
+
cheek: 0.28,
|
|
79
|
+
gazeY: -0.14,
|
|
80
|
+
squash: 0.16,
|
|
81
|
+
},
|
|
82
|
+
surprised: {
|
|
83
|
+
...NEUTRAL,
|
|
84
|
+
eyeUpper: 1.7,
|
|
85
|
+
eyeLower: 1.7,
|
|
86
|
+
eyeWidth: 1.16,
|
|
87
|
+
mouthUpper: 0,
|
|
88
|
+
mouthLower: 2.6,
|
|
89
|
+
mouthWidth: 0.62,
|
|
90
|
+
cheek: 0.12,
|
|
91
|
+
sparkle: 1,
|
|
92
|
+
gazeY: -0.2,
|
|
93
|
+
// -0.16, down from -0.24. This is the waking perk: `main.ts` wears
|
|
94
|
+
// `surprised` the instant she is told to wake, on top of a poke. At -0.24
|
|
95
|
+
// the pair drove the squash target past -0.34, which stretches her to
|
|
96
|
+
// roughly one and a half times her height -- a launch rather than an "oh!".
|
|
97
|
+
squash: -0.16,
|
|
98
|
+
},
|
|
99
|
+
thinking: {
|
|
100
|
+
...NEUTRAL,
|
|
101
|
+
eyeUpper: 0.9,
|
|
102
|
+
eyeLower: 0.72,
|
|
103
|
+
eyeTilt: 0.09,
|
|
104
|
+
mouthUpper: 1,
|
|
105
|
+
mouthLower: 0.3,
|
|
106
|
+
mouthWidth: 0.58,
|
|
107
|
+
gazeX: -0.52,
|
|
108
|
+
gazeY: -0.42,
|
|
109
|
+
lean: 0.07,
|
|
110
|
+
},
|
|
111
|
+
sleepy: {
|
|
112
|
+
...NEUTRAL,
|
|
113
|
+
eyeUpper: 0.16,
|
|
114
|
+
eyeLower: 0.16,
|
|
115
|
+
eyeTilt: -0.11,
|
|
116
|
+
mouthUpper: 0,
|
|
117
|
+
mouthLower: 1.5,
|
|
118
|
+
mouthWidth: 0.52,
|
|
119
|
+
// Gone. She is asleep; a mouth is the difference between resting and quiet.
|
|
120
|
+
mouthAlpha: 0,
|
|
121
|
+
cheek: 0.22,
|
|
122
|
+
gazeY: 0.34,
|
|
123
|
+
/*
|
|
124
|
+
0.11, up from 0.09, which had itself been cut from 0.19.
|
|
125
|
+
|
|
126
|
+
`sleepy` is what she wears for the whole time she is asleep, which is most
|
|
127
|
+
of her life, so it is the pose she is judged on -- and at 0.19 plus a
|
|
128
|
+
breath she spread to 1.24 times her width and read as a puddle rather than
|
|
129
|
+
as something resting. The cut was applied HERE, to the resting pose,
|
|
130
|
+
because the breath it was summed with reached +0.045 and could not be
|
|
131
|
+
argued with.
|
|
132
|
+
|
|
133
|
+
Back to 0.09, and the round trip is worth recording. It was raised to 0.11
|
|
134
|
+
when the breath became one-sided and small, on the argument that the same
|
|
135
|
+
0.135 ceiling bought a fuller resting pose. That argument was sound and the
|
|
136
|
+
premise did not survive: at the waking amplitude she moved 2px asleep and
|
|
137
|
+
read as switched off, so the asleep breath is twice as deep again (see
|
|
138
|
+
`ASLEEP_BREATH_GAIN`). With an excursion of 0.048 the base has to come back
|
|
139
|
+
down for the ceiling to stay where it was — 0.09 + 0.048 is 0.138, against
|
|
140
|
+
the 0.135 the pose plus breath already peaked at before any of this.
|
|
141
|
+
|
|
142
|
+
The pose is the same as it ever was, then. What changed is that the
|
|
143
|
+
movement on top of it is now all swell and no stretch.
|
|
144
|
+
*/
|
|
145
|
+
squash: 0.09,
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
const KEYS = Object.keys(NEUTRAL);
|
|
149
|
+
/**
|
|
150
|
+
* Neutral blended toward the named emotion by intensity.
|
|
151
|
+
*
|
|
152
|
+
* Intensity 0 is exactly neutral for every emotion, which is what lets a signal
|
|
153
|
+
* decay smoothly rather than snapping when it expires.
|
|
154
|
+
*/
|
|
155
|
+
export function blendLook(emotion, intensity) {
|
|
156
|
+
const target = LOOKS[emotion];
|
|
157
|
+
const t = clamp01(intensity);
|
|
158
|
+
const out = {};
|
|
159
|
+
// `a*(1-t) + b*t`, not `a + (b-a)*t`. The two are equivalent in real
|
|
160
|
+
// arithmetic and not in floating point: the second form returns
|
|
161
|
+
// 1.1000000000000001 rather than 1.1 at t = 1, so a fully applied emotion is
|
|
162
|
+
// never quite the look that was tuned. The endpoints are the values a human
|
|
163
|
+
// chose in the tuner, and they should survive the trip exactly.
|
|
164
|
+
for (const key of KEYS)
|
|
165
|
+
out[key] = NEUTRAL[key] * (1 - t) + target[key] * t;
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=looks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"looks.js","sourceRoot":"","sources":["../../src/core/looks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,168 @@
|
|
|
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
|
+
/**
|
|
32
|
+
* Everything a motion is allowed to move. See the header.
|
|
33
|
+
*
|
|
34
|
+
* ## The three that were added, and why they are not more
|
|
35
|
+
*
|
|
36
|
+
* `squash`, `lean`, `gazeX`, `gazeY` are postural and could not express a
|
|
37
|
+
* character who goes anywhere: a hop with no vertical channel is a crouch, and
|
|
38
|
+
* a wander with no horizontal one is a lean. So:
|
|
39
|
+
*
|
|
40
|
+
* - `lift` — up, in fractions of her body HEIGHT. Positive leaves the ground.
|
|
41
|
+
* - `shift` — right, in fractions of her body WIDTH.
|
|
42
|
+
* - `turn` — -1..1, how far her features have slid toward one side.
|
|
43
|
+
*
|
|
44
|
+
* `turn` is NOT a rotation and cannot become one. One front-facing silhouette
|
|
45
|
+
* is drawn; there is no second view and no yaw. What it does is slide the
|
|
46
|
+
* features across the body, and everything is clipped to the silhouette
|
|
47
|
+
* already, so the far eye passes out of sight around her edge. That reads as
|
|
48
|
+
* turning to look at something and does not read as turning around. Anyone
|
|
49
|
+
* wanting the second thing has to draw her from behind, which is art rather
|
|
50
|
+
* than a channel.
|
|
51
|
+
*
|
|
52
|
+
* The mouth is still not among these, for the reason the header gives.
|
|
53
|
+
*
|
|
54
|
+
* ## Translation is a channel, not a window move
|
|
55
|
+
*
|
|
56
|
+
* `lift` and `shift` move her INSIDE her own window, and `paint` applies them
|
|
57
|
+
* by offsetting the origin every other coordinate is derived from -- so the
|
|
58
|
+
* outline, the features and the hit-test silhouette all travel together, and
|
|
59
|
+
* click-through keeps following her painted pixels for free.
|
|
60
|
+
*
|
|
61
|
+
* Moving the WINDOW was the alternative and is worse: `setPosition` from main
|
|
62
|
+
* on every frame of a renderer-driven animation is two processes disagreeing
|
|
63
|
+
* about where she is sixty times a second, which is the problem `drag.ts`
|
|
64
|
+
* already needs a cursor poll to solve.
|
|
65
|
+
*/
|
|
66
|
+
export declare const MOTION_CHANNELS: readonly ['squash', 'lean', 'gazeX', 'gazeY', 'lift', 'shift', 'turn'];
|
|
67
|
+
export type MotionChannel = (typeof MOTION_CHANNELS)[number];
|
|
68
|
+
/** What the layer contributes at one instant. Absent means "not moved". */
|
|
69
|
+
export type MotionPose = Partial<Record<MotionChannel, number>>;
|
|
70
|
+
export interface MotionKey extends MotionPose {
|
|
71
|
+
/**
|
|
72
|
+
* Where in the clip, 0..1.
|
|
73
|
+
*
|
|
74
|
+
* NORMALISED rather than milliseconds. The two built-ins differ in length by
|
|
75
|
+
* six times, and a clip timed in absolute units cannot be slowed down
|
|
76
|
+
* without rewriting every key in it.
|
|
77
|
+
*/
|
|
78
|
+
readonly t: number;
|
|
79
|
+
}
|
|
80
|
+
export interface MotionClip {
|
|
81
|
+
readonly durationMs: number;
|
|
82
|
+
/** Whether it starts again. `sway` does; `nod` must not. */
|
|
83
|
+
readonly loop: boolean;
|
|
84
|
+
/** Sorted by `t`, which `parseMotionClip` checks rather than assumes. */
|
|
85
|
+
readonly keys: readonly MotionKey[];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* How far through a clip we are, or null once a one-shot has finished.
|
|
89
|
+
*
|
|
90
|
+
* Null rather than 1: a finished one-shot contributes NOTHING, and holding its
|
|
91
|
+
* last key would leave her permanently leaning if a clip happened to end away
|
|
92
|
+
* from neutral. The caller stops mixing the layer in at all.
|
|
93
|
+
*/
|
|
94
|
+
export declare function progress(clip: MotionClip, elapsedMs: number): number | null;
|
|
95
|
+
/**
|
|
96
|
+
* The pose at a moment, interpolated between the keys around it.
|
|
97
|
+
*
|
|
98
|
+
* Per CHANNEL, not per key. A key that omits `gazeX` is not saying "zero", it
|
|
99
|
+
* is saying nothing -- so the interpolation for that channel runs between the
|
|
100
|
+
* nearest keys that DO mention it, and a channel no key mentions is absent
|
|
101
|
+
* from the result. Blending toward an implied zero instead would make every
|
|
102
|
+
* clip fight every other layer over channels it never meant to touch.
|
|
103
|
+
*/
|
|
104
|
+
export declare function poseAt(clip: MotionClip, at: number): MotionPose;
|
|
105
|
+
/**
|
|
106
|
+
* A short agreement, and a long one that repeats.
|
|
107
|
+
*
|
|
108
|
+
* Two, and unalike on purpose -- see the header. They are also the only
|
|
109
|
+
* motions this app has, which is why `caps.supportsMotions` can finally be
|
|
110
|
+
* true: a backend claiming the capability with an empty library would be the
|
|
111
|
+
* flag lying, and `playMotion` would report success over a face that never
|
|
112
|
+
* moved.
|
|
113
|
+
*/
|
|
114
|
+
export declare const BUILT_IN_MOTIONS: Readonly<Record<string, MotionClip>>;
|
|
115
|
+
/**
|
|
116
|
+
* How far a clip takes her from where she stands, as fractions of her body.
|
|
117
|
+
*
|
|
118
|
+
* The room for a translation has to exist in her window BEFORE she uses it, or
|
|
119
|
+
* she walks into the edge of a transparent rectangle and is clipped by it. This
|
|
120
|
+
* is what `face.ts` reserves against, and it is derived from the keys rather
|
|
121
|
+
* than declared beside them: a number an author has to keep in step with the
|
|
122
|
+
* clip is a number that goes stale the first time somebody retimes it.
|
|
123
|
+
*
|
|
124
|
+
* `up` only, for `lift`. She hops; she does not sink through the floor, and
|
|
125
|
+
* reserving room below her would push her down inside her own window for a
|
|
126
|
+
* clearance nothing uses.
|
|
127
|
+
*/
|
|
128
|
+
export declare function motionReach(clip: MotionClip): {
|
|
129
|
+
readonly x: number;
|
|
130
|
+
readonly up: number;
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* The worst case across every built-in, PLUS the drift, which is what a window
|
|
134
|
+
* has to hold.
|
|
135
|
+
*
|
|
136
|
+
* The drift is continuous and additive -- she is always a little off her spot,
|
|
137
|
+
* and a clip starts from wherever that is rather than from centre. So the two
|
|
138
|
+
* reaches SUM: reserving only the clip's would clip her by a couple of pixels
|
|
139
|
+
* at whichever extreme the two happened to agree on, intermittently, which is
|
|
140
|
+
* the least debuggable kind of wrong.
|
|
141
|
+
*/
|
|
142
|
+
export declare function builtInReach(): {
|
|
143
|
+
readonly x: number;
|
|
144
|
+
readonly up: number;
|
|
145
|
+
};
|
|
146
|
+
export type MotionParse = {
|
|
147
|
+
readonly ok: true;
|
|
148
|
+
readonly clip: MotionClip;
|
|
149
|
+
} | {
|
|
150
|
+
readonly ok: false;
|
|
151
|
+
readonly problems: readonly string[];
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* Turn something off disk into a clip, or say what is wrong with all of it.
|
|
155
|
+
*
|
|
156
|
+
* A package may carry motions, so this is a trust boundary and takes the same
|
|
157
|
+
* treatment `parseFaceSpec` gets: every problem reported at once
|
|
158
|
+
* rather than the first, unknown keys refused rather than dropped, and every
|
|
159
|
+
* number range-checked -- `t: 5` and `squash: 400` are both valid JSON numbers
|
|
160
|
+
* that throw nothing and produce a mochi somewhere off the screen.
|
|
161
|
+
*
|
|
162
|
+
* The ordering and loop-closure rules are checked HERE rather than trusted,
|
|
163
|
+
* because `poseAt` is written against them: keys out of order make the
|
|
164
|
+
* interpolation pick the wrong pair, and a loop whose ends differ jumps once
|
|
165
|
+
* per cycle for as long as it plays.
|
|
166
|
+
*/
|
|
167
|
+
export declare function parseMotionClip(value: unknown): MotionParse;
|
|
168
|
+
//# sourceMappingURL=motion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"motion.d.ts","sourceRoot":"","sources":["../../src/core/motion.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAIH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,eAAe,YAC1B,QAAQ,EACR,MAAM,EACN,OAAO,EACP,OAAO,EACP,MAAM,EACN,OAAO,EACP,MAAM,CACE,CAAA;AACV,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAA;AAE5D,2EAA2E;AAC3E,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAA;AAE/D,MAAM,WAAW,SAAU,SAAQ,UAAU;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,yEAAyE;IACzE,QAAQ,CAAC,IAAI,EAAE,SAAS,SAAS,EAAE,CAAA;CACpC;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK3E;AAED;;;;;;;;GAQG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,GAAG,UAAU,CAO/D;AAwID;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAyJjE,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG;IAAE,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAQzF;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,IAAI;IAAE,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAS1E;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GAChD;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAA;AAEhE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CA0F3D"}
|