@px-petals/roku.motion-web 0.0.0-stage → 1.1.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/LICENSE +201 -0
- package/audio/audio.cues.js +27 -0
- package/audio/audio.js +259 -0
- package/bands/bands.js +392 -0
- package/bundle/bundle.fonts.js +55 -0
- package/bundle/bundle.js +131 -0
- package/focus/focus.direction.js +46 -0
- package/focus/focus.dom.js +158 -0
- package/focus/focus.js +277 -0
- package/focus/focus.tree.js +146 -0
- package/hit/hit.geometry.js +113 -0
- package/hit/hit.js +129 -0
- package/list/list.flex.js +181 -0
- package/list/list.items.js +80 -0
- package/list/list.js +518 -0
- package/lottie/lottie.js +124 -0
- package/lottie/lottie.layers.js +416 -0
- package/lottie/lottie.paint.js +238 -0
- package/lottie/lottie.paths.js +474 -0
- package/lottie/lottie.props.js +265 -0
- package/lottie/lottie.shapes.js +259 -0
- package/mixer/mixer.js +307 -0
- package/mixer/mixer.mix.js +107 -0
- package/mixer/mixer.stage.js +431 -0
- package/motion/motion.convert.js +404 -0
- package/motion/motion.flow.js +421 -0
- package/motion/motion.keys.js +150 -0
- package/motion/motion.machine.js +382 -0
- package/motion/motion.nested.js +345 -0
- package/motion/motion.values.js +368 -0
- package/motion.js +1155 -0
- package/package.json +47 -4
- package/player/overlay/overlay.js +325 -0
- package/player/overlay/overlay.layout.js +208 -0
- package/player/player.fit.js +101 -0
- package/player/player.js +421 -0
- package/player/player.raster.js +127 -0
- package/pointer/pointer.js +292 -0
- package/readme.md +345 -0
- package/script/luau/lib/base/base.js +202 -0
- package/script/luau/lib/base/base.table.js +189 -0
- package/script/luau/lib/lib.args.js +152 -0
- package/script/luau/lib/lib.js +23 -0
- package/script/luau/lib/math/math.bit32.js +144 -0
- package/script/luau/lib/math/math.js +288 -0
- package/script/luau/lib/math/math.vector.js +125 -0
- package/script/luau/lib/string/string.format.js +143 -0
- package/script/luau/lib/string/string.js +160 -0
- package/script/luau/lib/string/string.pattern.js +505 -0
- package/script/luau/load/load.js +362 -0
- package/script/luau/load/load.ops.js +79 -0
- package/script/luau/luau.convert.js +259 -0
- package/script/luau/luau.js +132 -0
- package/script/luau/luau.md +111 -0
- package/script/luau/vm/vm.js +167 -0
- package/script/luau/vm/vm.meta.js +401 -0
- package/script/luau/vm/vm.num.js +236 -0
- package/script/luau/vm/vm.run.js +842 -0
- package/script/luau/vm/vm.value.js +406 -0
- package/script/play/play.js +199 -0
- package/script/play/play.objects.js +295 -0
- package/script/play/play.scope.js +91 -0
- package/script/rive/api/api.statics.js +31 -0
- package/script/rive/api/api.type.js +188 -0
- package/script/rive/api/artboard/artboard.js +89 -0
- package/script/rive/api/assets/assets.js +72 -0
- package/script/rive/api/data/data.value.js +99 -0
- package/script/rive/api/draw/draw.contours.js +163 -0
- package/script/rive/api/draw/draw.measure.js +196 -0
- package/script/rive/api/draw/draw.paint.js +235 -0
- package/script/rive/api/draw/draw.path.js +153 -0
- package/script/rive/api/draw/draw.renderer.js +117 -0
- package/script/rive/api/math/math.color.js +96 -0
- package/script/rive/api/math/math.mat2d.js +180 -0
- package/script/rive/api/math/math.vector.js +121 -0
- package/script/rive/api/media/media.audio.js +113 -0
- package/script/rive/api/media/media.draw.js +73 -0
- package/script/rive/api/media/media.image.js +61 -0
- package/script/rive/api/media/media.mesh.js +50 -0
- package/script/rive/api/pointer/pointer.event.js +64 -0
- package/script/rive/api/pointer/pointer.route.js +69 -0
- package/script/rive/api/text/text.font.js +129 -0
- package/script/rive/api/text/text.js +278 -0
- package/script/rive/api/text/text.layout.js +259 -0
- package/script/rive/context/context.js +76 -0
- package/script/rive/context/context.memory.js +196 -0
- package/script/rive/context/context.viewmodel.js +302 -0
- package/script/rive/context/lists/lists.js +160 -0
- package/script/rive/context/lists/lists.values.js +281 -0
- package/script/rive/host/host.globals.js +44 -0
- package/script/rive/host/host.js +533 -0
- package/script/rive/host/host.node.js +45 -0
- package/script/rive/rive.js +23 -0
- package/script/rive/rive.md +180 -0
- package/script/script.js +11 -0
- package/skin/skin.deform.js +514 -0
- package/skin/skin.ik.js +260 -0
- package/skin/skin.js +297 -0
- package/README.md +0 -3
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lottie properties as functions of the frame: a static value, or keyframes
|
|
3
|
+
* eased by their cubic beziers. A property carrying a slot id (`sid`) reads
|
|
4
|
+
* the live slot table first, so data bindings change what draws without
|
|
5
|
+
* recompiling the animation.
|
|
6
|
+
*
|
|
7
|
+
* Both keyframe forms are read: the current one, where a segment ends at the
|
|
8
|
+
* next keyframe's `s`, and the older one lottie-roku's sources still use,
|
|
9
|
+
* where a keyframe carries its own end value in `e`.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { mixValues, settledWeight } from '../mixer/mixer.mix.js'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {(frame: number) => any} Evaluator
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Live slot values, by slot id; `undefined` leaves a property to its keyframes, and a function gives the value at a frame.
|
|
20
|
+
* @typedef {Record<string, any>} Slots
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {any} property a Lottie property: `{ a, k }`, optionally with `sid`
|
|
25
|
+
* @param {Slots} slots read on every call, so changing it changes the result
|
|
26
|
+
* @param {any} [fallback] the value when the property is absent
|
|
27
|
+
* @returns {Evaluator}
|
|
28
|
+
*/
|
|
29
|
+
export function evaluator(property, slots, fallback) {
|
|
30
|
+
if (!property) return () => fallback
|
|
31
|
+
|
|
32
|
+
const keyed = property.pz ? posedEvaluator(property, keyedEvaluator(property, fallback), slots) : keyedEvaluator(property, fallback)
|
|
33
|
+
const sid = property.sid
|
|
34
|
+
if (typeof sid !== 'string') return keyed
|
|
35
|
+
|
|
36
|
+
// a nested machine that mixes several animations names where its poses are
|
|
37
|
+
const poseKey = property.pz ? 'pz:' + property.pz.n : ''
|
|
38
|
+
return (frame) => {
|
|
39
|
+
const live = slots[sid]
|
|
40
|
+
// a live value can depend on the frame: a skin the animation keys is asked for its path, in the poses of the machine it is drawn in
|
|
41
|
+
if (typeof live === 'function') return live(frame, poseKey ? slots[poseKey] : null)
|
|
42
|
+
return live === undefined ? keyed(frame) : live
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A property of a nested machine that plays several animations at once. The
|
|
48
|
+
* machine says which it plays, as `{ start, frame, weight }` in the slot
|
|
49
|
+
* `pz:<id>`: the first frame of the animation's segment on the stitched
|
|
50
|
+
* timeline, how far in it is, and its weight. Rive applies the poses in
|
|
51
|
+
* order, each mixing the value toward its own by its weight, onto what the
|
|
52
|
+
* last frame left: held, that settles where each pose that keys the property
|
|
53
|
+
* counts by its weight times what the poses after it leave (`settledWeight`),
|
|
54
|
+
* and the setup value no longer shows. One that does not key it leaves it
|
|
55
|
+
* alone; with none keying it, it is the keyframes at the time remap's frame.
|
|
56
|
+
*
|
|
57
|
+
* @param {any} property `pz` is `{ n, k }`: the machine's id, and the segments that key the property
|
|
58
|
+
* @param {Evaluator} keyed
|
|
59
|
+
* @param {Slots} slots
|
|
60
|
+
* @returns {Evaluator}
|
|
61
|
+
*/
|
|
62
|
+
function posedEvaluator(property, keyed, slots) {
|
|
63
|
+
const key = 'pz:' + property.pz.n
|
|
64
|
+
const keying = property.pz.k
|
|
65
|
+
return (frame) => {
|
|
66
|
+
const poses = slots[key]
|
|
67
|
+
const value = keyed(frame)
|
|
68
|
+
if (!poses) return value
|
|
69
|
+
|
|
70
|
+
let mixed = value
|
|
71
|
+
let total = 0
|
|
72
|
+
for (let n = 0; n < poses.length; n++) {
|
|
73
|
+
const pose = poses[n]
|
|
74
|
+
const weight = keying.indexOf(pose.start) < 0 ? 0 : settledWeight(poses, n, keying)
|
|
75
|
+
if (weight <= 0) continue
|
|
76
|
+
// a running weighted average: each pose's share of what has been counted so far
|
|
77
|
+
total += weight
|
|
78
|
+
mixed = mixValues(mixed, keyed(pose.start + pose.frame), weight / total)
|
|
79
|
+
}
|
|
80
|
+
return mixed
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Whether a property's value changes over time.
|
|
86
|
+
*
|
|
87
|
+
* @param {any} property
|
|
88
|
+
*/
|
|
89
|
+
export function isAnimated(property) {
|
|
90
|
+
return !!property && property.a === 1 && Array.isArray(property.k) && property.k.length > 0 && typeof property.k[0] === 'object' && property.k[0] !== null && 't' in property.k[0]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {any} property
|
|
95
|
+
* @param {any} fallback
|
|
96
|
+
* @returns {Evaluator}
|
|
97
|
+
*/
|
|
98
|
+
function keyedEvaluator(property, fallback) {
|
|
99
|
+
// the pose mixer replaces a keyed property's keyframes with its own evaluator
|
|
100
|
+
if (typeof property.mx === 'function') return property.mx
|
|
101
|
+
|
|
102
|
+
if (!isAnimated(property)) {
|
|
103
|
+
const value = property.k === undefined ? fallback : property.k
|
|
104
|
+
return () => value
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const keyframes = property.k
|
|
108
|
+
return (frame) => valueAt(keyframes, frame)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @param {any[]} keyframes
|
|
113
|
+
* @param {number} frame
|
|
114
|
+
* @returns {any}
|
|
115
|
+
*/
|
|
116
|
+
function valueAt(keyframes, frame) {
|
|
117
|
+
const first = keyframes[0]
|
|
118
|
+
if (frame <= first.t || keyframes.length === 1) return startValue(first)
|
|
119
|
+
|
|
120
|
+
for (let n = 0; n < keyframes.length - 1; n++) {
|
|
121
|
+
const from = keyframes[n]
|
|
122
|
+
const to = keyframes[n + 1]
|
|
123
|
+
if (frame >= to.t) continue
|
|
124
|
+
|
|
125
|
+
if (from.h === 1) return startValue(from)
|
|
126
|
+
const end = from.e !== undefined ? from.e : startValue(to)
|
|
127
|
+
const span = to.t - from.t
|
|
128
|
+
const progress = span > 0 ? (frame - from.t) / span : 1
|
|
129
|
+
return blend(startValue(from), end, from, progress)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const last = keyframes[keyframes.length - 1]
|
|
133
|
+
// an old-form last keyframe may hold no `s`; the one before ends where it ends
|
|
134
|
+
if (last.s === undefined) {
|
|
135
|
+
const before = keyframes[keyframes.length - 2]
|
|
136
|
+
return before.e !== undefined ? before.e : startValue(before)
|
|
137
|
+
}
|
|
138
|
+
return startValue(last)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** @param {any} keyframe */
|
|
142
|
+
function startValue(keyframe) {
|
|
143
|
+
return keyframe.s
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Eases each dimension by its own bezier when the keyframe gives one per
|
|
148
|
+
* dimension, else all by the same one.
|
|
149
|
+
*
|
|
150
|
+
* @param {any} from
|
|
151
|
+
* @param {any} to
|
|
152
|
+
* @param {any} keyframe the segment's first keyframe, holding `o` and `i`
|
|
153
|
+
* @param {number} progress 0-1 through the segment
|
|
154
|
+
*/
|
|
155
|
+
function blend(from, to, keyframe, progress) {
|
|
156
|
+
if (isPath(from)) return blendPath(from, to, easeAt(keyframe, 0, progress))
|
|
157
|
+
if (!Array.isArray(from)) return lerp(from, to, easeAt(keyframe, 0, progress))
|
|
158
|
+
|
|
159
|
+
const result = []
|
|
160
|
+
for (let d = 0; d < from.length; d++) {
|
|
161
|
+
const target = Array.isArray(to) && d < to.length ? to[d] : from[d]
|
|
162
|
+
result.push(lerp(from[d], target, easeAt(keyframe, d, progress)))
|
|
163
|
+
}
|
|
164
|
+
return result
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @param {any} value
|
|
169
|
+
*/
|
|
170
|
+
function isPath(value) {
|
|
171
|
+
return Array.isArray(value) && value.length > 0 && typeof value[0] === 'object' && value[0] !== null && Array.isArray(value[0].v)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* A shape keyframe's value is `[{ i, o, v, c }]`; its vertices blend pairwise.
|
|
176
|
+
*
|
|
177
|
+
* @param {any[]} from
|
|
178
|
+
* @param {any[]} to
|
|
179
|
+
* @param {number} t
|
|
180
|
+
*/
|
|
181
|
+
function blendPath(from, to, t) {
|
|
182
|
+
const a = from[0]
|
|
183
|
+
const b = Array.isArray(to) && to[0] ? to[0] : a
|
|
184
|
+
return [ { c: a.c, v: blendPoints(a.v, b.v, t), i: blendPoints(a.i, b.i, t), o: blendPoints(a.o, b.o, t) } ]
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @param {number[][]} a
|
|
189
|
+
* @param {number[][]} b
|
|
190
|
+
* @param {number} t
|
|
191
|
+
*/
|
|
192
|
+
function blendPoints(a, b, t) {
|
|
193
|
+
const points = []
|
|
194
|
+
for (let n = 0; n < a.length; n++) {
|
|
195
|
+
const q = b[n] || a[n]
|
|
196
|
+
points.push([ lerp(a[n][0], q[0], t), lerp(a[n][1], q[1], t) ])
|
|
197
|
+
}
|
|
198
|
+
return points
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* @param {number} a
|
|
203
|
+
* @param {number} b
|
|
204
|
+
* @param {number} t
|
|
205
|
+
*/
|
|
206
|
+
function lerp(a, b, t) {
|
|
207
|
+
return a + (b - a) * t
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* The eased progress for one dimension.
|
|
212
|
+
*
|
|
213
|
+
* @param {any} keyframe
|
|
214
|
+
* @param {number} dimension
|
|
215
|
+
* @param {number} progress
|
|
216
|
+
* @returns {number}
|
|
217
|
+
*/
|
|
218
|
+
function easeAt(keyframe, dimension, progress) {
|
|
219
|
+
const out = keyframe.o
|
|
220
|
+
const into = keyframe.i
|
|
221
|
+
if (!out || !into) return progress
|
|
222
|
+
|
|
223
|
+
const pick = (/** @type {any} */ value) => (Array.isArray(value) ? (dimension < value.length ? value[dimension] : value[0]) : value)
|
|
224
|
+
return cubicBezier(pick(out.x), pick(out.y), pick(into.x), pick(into.y), progress)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* The y of a CSS-style cubic bezier easing curve (0,0)-(x1,y1)-(x2,y2)-(1,1)
|
|
229
|
+
* at x: Newton's method on x, bisection when it stalls.
|
|
230
|
+
*
|
|
231
|
+
* @param {number} x1
|
|
232
|
+
* @param {number} y1
|
|
233
|
+
* @param {number} x2
|
|
234
|
+
* @param {number} y2
|
|
235
|
+
* @param {number} x
|
|
236
|
+
* @returns {number}
|
|
237
|
+
*/
|
|
238
|
+
export function cubicBezier(x1, y1, x2, y2, x) {
|
|
239
|
+
const isLinear = x1 === y1 && x2 === y2
|
|
240
|
+
if (isLinear || x <= 0 || x >= 1) return x <= 0 ? 0 : x >= 1 ? 1 : x
|
|
241
|
+
|
|
242
|
+
const curve = (/** @type {number} */ t, /** @type {number} */ p1, /** @type {number} */ p2) => 3 * (1 - t) * (1 - t) * t * p1 + 3 * (1 - t) * t * t * p2 + t * t * t
|
|
243
|
+
const slope = (/** @type {number} */ t, /** @type {number} */ p1, /** @type {number} */ p2) => 3 * (1 - t) * (1 - t) * p1 + 6 * (1 - t) * t * (p2 - p1) + 3 * t * t * (1 - p2)
|
|
244
|
+
|
|
245
|
+
let t = x
|
|
246
|
+
for (let n = 0; n < 8; n++) {
|
|
247
|
+
const error = curve(t, x1, x2) - x
|
|
248
|
+
if (Math.abs(error) < 1e-6) return curve(t, y1, y2)
|
|
249
|
+
const d = slope(t, x1, x2)
|
|
250
|
+
if (Math.abs(d) < 1e-6) break
|
|
251
|
+
t -= error / d
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
let low = 0
|
|
255
|
+
let high = 1
|
|
256
|
+
t = x
|
|
257
|
+
for (let n = 0; n < 40; n++) {
|
|
258
|
+
const value = curve(t, x1, x2)
|
|
259
|
+
if (Math.abs(value - x) < 1e-6) break
|
|
260
|
+
if (value < x) low = t
|
|
261
|
+
else high = t
|
|
262
|
+
t = (low + high) / 2
|
|
263
|
+
}
|
|
264
|
+
return curve(t, y1, y2)
|
|
265
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A shape layer's items, compiled once and run per frame into draw
|
|
3
|
+
* operations.
|
|
4
|
+
*
|
|
5
|
+
* Lottie's rules, in a group's item list:
|
|
6
|
+
* - earlier items are on top, so operations draw in reverse;
|
|
7
|
+
* - a paint covers every path above it in its group, and in groups above it;
|
|
8
|
+
* - a modifier (trim, merge) changes those same paths, so a subgroup's own
|
|
9
|
+
* paint draws them trimmed too.
|
|
10
|
+
*
|
|
11
|
+
* Geometry therefore lives in a shared holder: a trim rewrites the holder's
|
|
12
|
+
* paths, and every paint that captured it draws the result.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { evaluator } from './lottie.props.js'
|
|
16
|
+
import { ellipse, fromLottie, IDENTITY, multiply, rectangle, star, transformMatrix, trimEach } from './lottie.paths.js'
|
|
17
|
+
|
|
18
|
+
/** @import { Path, Matrix } from './lottie.paths.js' */
|
|
19
|
+
/** @import { Slots } from './lottie.props.js' */
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Geometry gathered from items above a paint or modifier.
|
|
23
|
+
* @typedef {{ paths: Path[], merge?: { mode: number, parts: Entry[] } }} Holder
|
|
24
|
+
* @typedef {{ holder: Holder, matrix: Matrix }} Entry `matrix` maps the holder's space into the gathering group's
|
|
25
|
+
* @typedef {{ paint: any, entries: Entry[], matrix: Matrix, opacity: number }} Operation `matrix` maps the paint's group into the layer
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {any[]} items a shape layer's `shapes`, or a group's `it`
|
|
30
|
+
* @param {Slots} slots
|
|
31
|
+
* @returns {any[]} compiled items
|
|
32
|
+
*/
|
|
33
|
+
export function compileItems(items, slots) {
|
|
34
|
+
const compiled = []
|
|
35
|
+
for (let n = 0; n < (items || []).length; n++) {
|
|
36
|
+
const item = items[n]
|
|
37
|
+
if (item.hd) continue
|
|
38
|
+
const one = compileItem(item, slots)
|
|
39
|
+
if (one) compiled.push(one)
|
|
40
|
+
}
|
|
41
|
+
return compiled
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {any} item
|
|
46
|
+
* @param {Slots} slots
|
|
47
|
+
*/
|
|
48
|
+
function compileItem(item, slots) {
|
|
49
|
+
const at = (/** @type {any} */ property, /** @type {any} */ fallback) => evaluator(property, slots, fallback)
|
|
50
|
+
const reversed = item.d === 3
|
|
51
|
+
|
|
52
|
+
switch (item.ty) {
|
|
53
|
+
case 'gr':
|
|
54
|
+
return { kind: 'group', rid: typeof item.rid === 'string' ? item.rid : '', items: compileItems(item.it, slots), transform: compileTransform((item.it || []).filter((/** @type {any} */ i) => i.ty === 'tr')[0], slots) }
|
|
55
|
+
case 'sh': {
|
|
56
|
+
const shape = at(item.ks, { v: [], i: [], o: [], c: false })
|
|
57
|
+
return { kind: 'geometry', path: (/** @type {number} */ f) => fromLottie(unwrap(shape(f))) }
|
|
58
|
+
}
|
|
59
|
+
case 'rc': {
|
|
60
|
+
const p = at(item.p, [ 0, 0 ])
|
|
61
|
+
const s = at(item.s, [ 0, 0 ])
|
|
62
|
+
const r = at(item.r, 0)
|
|
63
|
+
return { kind: 'geometry', path: (/** @type {number} */ f) => rectangle(p(f), s(f), scalar(r(f)), reversed) }
|
|
64
|
+
}
|
|
65
|
+
case 'el': {
|
|
66
|
+
const p = at(item.p, [ 0, 0 ])
|
|
67
|
+
const s = at(item.s, [ 0, 0 ])
|
|
68
|
+
return { kind: 'geometry', path: (/** @type {number} */ f) => ellipse(p(f), s(f), reversed) }
|
|
69
|
+
}
|
|
70
|
+
case 'sr': {
|
|
71
|
+
const p = at(item.p, [ 0, 0 ])
|
|
72
|
+
const pt = at(item.pt, 5)
|
|
73
|
+
const r = at(item.r, 0)
|
|
74
|
+
const or = at(item.or, 0)
|
|
75
|
+
const ir = at(item.ir, 0)
|
|
76
|
+
const os = at(item.os, 0)
|
|
77
|
+
const is = at(item.is, 0)
|
|
78
|
+
return {
|
|
79
|
+
kind: 'geometry',
|
|
80
|
+
path: (/** @type {number} */ f) => star({ type: item.sy, points: scalar(pt(f)), position: p(f), rotation: scalar(r(f)), outer: scalar(or(f)), inner: scalar(ir(f)), outerRound: scalar(os(f)), innerRound: scalar(is(f)), reversed }),
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
case 'tm':
|
|
84
|
+
return { kind: 'trim', start: at(item.s, 0), end: at(item.e, 100), offset: at(item.o, 0), together: item.m === 2 }
|
|
85
|
+
case 'mm':
|
|
86
|
+
return { kind: 'merge', mode: item.mm || 1 }
|
|
87
|
+
case 'fl':
|
|
88
|
+
case 'st':
|
|
89
|
+
case 'gf':
|
|
90
|
+
case 'gs':
|
|
91
|
+
return { kind: 'paint', paint: compilePaint(item, slots) }
|
|
92
|
+
default:
|
|
93
|
+
// `tr` is read by its group; repeaters, round corners and the rest are not drawn
|
|
94
|
+
return null
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* A shape keyframe value arrives wrapped in a one-element array.
|
|
100
|
+
*
|
|
101
|
+
* @param {any} value
|
|
102
|
+
*/
|
|
103
|
+
function unwrap(value) {
|
|
104
|
+
return Array.isArray(value) ? value[0] || { v: [] } : value
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** @param {any} value */
|
|
108
|
+
export function scalar(value) {
|
|
109
|
+
return Array.isArray(value) ? value[0] : value
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @param {any} tr
|
|
114
|
+
* @param {Slots} slots
|
|
115
|
+
* @returns {(frame: number) => { matrix: Matrix, opacity: number }}
|
|
116
|
+
*/
|
|
117
|
+
export function compileTransform(tr, slots) {
|
|
118
|
+
if (!tr) return () => ({ matrix: IDENTITY, opacity: 1 })
|
|
119
|
+
|
|
120
|
+
const at = (/** @type {any} */ property, /** @type {any} */ fallback) => evaluator(property, slots, fallback)
|
|
121
|
+
const a = at(tr.a, [ 0, 0 ])
|
|
122
|
+
const p = splitPosition(tr.p, slots)
|
|
123
|
+
const s = at(tr.s, [ 100, 100 ])
|
|
124
|
+
const r = at(tr.r || tr.rz, 0)
|
|
125
|
+
const o = at(tr.o, 100)
|
|
126
|
+
const sk = at(tr.sk, 0)
|
|
127
|
+
const sa = at(tr.sa, 0)
|
|
128
|
+
return (frame) => ({
|
|
129
|
+
matrix: transformMatrix(a(frame), p(frame), s(frame), scalar(r(frame)), scalar(sk(frame)), scalar(sa(frame))),
|
|
130
|
+
opacity: scalar(o(frame)) / 100,
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* A position, given whole or as separate `x` and `y` (`s: true`).
|
|
136
|
+
*
|
|
137
|
+
* @param {any} position
|
|
138
|
+
* @param {Slots} slots
|
|
139
|
+
*/
|
|
140
|
+
function splitPosition(position, slots) {
|
|
141
|
+
if (position && position.s) {
|
|
142
|
+
const x = evaluator(position.x, slots, 0)
|
|
143
|
+
const y = evaluator(position.y, slots, 0)
|
|
144
|
+
return (/** @type {number} */ frame) => [ scalar(x(frame)), scalar(y(frame)) ]
|
|
145
|
+
}
|
|
146
|
+
return evaluator(position, slots, [ 0, 0 ])
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @param {any} item
|
|
151
|
+
* @param {Slots} slots
|
|
152
|
+
*/
|
|
153
|
+
function compilePaint(item, slots) {
|
|
154
|
+
const at = (/** @type {any} */ property, /** @type {any} */ fallback) => evaluator(property, slots, fallback)
|
|
155
|
+
const isStroke = item.ty === 'st' || item.ty === 'gs'
|
|
156
|
+
const isGradient = item.ty === 'gf' || item.ty === 'gs'
|
|
157
|
+
return {
|
|
158
|
+
isStroke,
|
|
159
|
+
isGradient,
|
|
160
|
+
rule: item.r === 2 ? 'evenodd' : 'nonzero',
|
|
161
|
+
opacity: at(item.o, 100),
|
|
162
|
+
color: at(item.c, [ 0, 0, 0, 1 ]),
|
|
163
|
+
width: at(item.w, 1),
|
|
164
|
+
cap: [ 'butt', 'butt', 'round', 'square' ][item.lc || 1],
|
|
165
|
+
join: [ 'miter', 'miter', 'round', 'bevel' ][item.lj || 1],
|
|
166
|
+
miter: item.ml || 4,
|
|
167
|
+
dashes: (item.d || []).map((/** @type {any} */ d) => ({ kind: d.n, value: at(d.v, 0) })),
|
|
168
|
+
gradient: isGradient
|
|
169
|
+
? { type: item.t || 1, start: at(item.s, [ 0, 0 ]), end: at(item.e, [ 0, 0 ]), stops: at(item.g && item.g.k, []), count: (item.g && item.g.p) || 0, highlight: at(item.h, 0), angle: at(item.a, 0) }
|
|
170
|
+
: null,
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Running
|
|
175
|
+
// ■■■■■■■
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Where a frame's hit-test geometry goes: groups tagged with a watched `rid` hand over their paths.
|
|
179
|
+
* @typedef {object} HitSink
|
|
180
|
+
* @property {import('../hit/hit.js').Hits} hits
|
|
181
|
+
* @property {Matrix} matrix the layer's space onto the canvas
|
|
182
|
+
* @property {string} prefix what a group's `rid` is named under: a nested machine's artboard has its own, so it is `<the layer's nid>/`; "" in the animation's own
|
|
183
|
+
*/
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Runs a group's items at a frame.
|
|
187
|
+
*
|
|
188
|
+
* @param {any[]} items compiled items
|
|
189
|
+
* @param {number} frame
|
|
190
|
+
* @param {Matrix} matrix the group's space into the layer's
|
|
191
|
+
* @param {number} opacity the group's opacity, compounded
|
|
192
|
+
* @param {HitSink | null} [sink] records the paths of groups a pointer listener targets
|
|
193
|
+
* @returns {{ entries: Entry[], operations: Operation[] }} the paths above, for the parent's paints, and what to draw, top first
|
|
194
|
+
*/
|
|
195
|
+
export function runGroup(items, frame, matrix, opacity, sink) {
|
|
196
|
+
/** @type {Entry[]} */
|
|
197
|
+
let entries = []
|
|
198
|
+
/** @type {Operation[]} */
|
|
199
|
+
const operations = []
|
|
200
|
+
|
|
201
|
+
for (let n = 0; n < items.length; n++) {
|
|
202
|
+
const item = items[n]
|
|
203
|
+
if (item.kind === 'geometry') {
|
|
204
|
+
entries.push({ holder: { paths: [ item.path(frame) ] }, matrix: IDENTITY })
|
|
205
|
+
} else if (item.kind === 'group') {
|
|
206
|
+
const local = item.transform(frame)
|
|
207
|
+
const inner = runGroup(item.items, frame, multiply(matrix, local.matrix), opacity * local.opacity, sink)
|
|
208
|
+
// a group that draws nothing (a Solo's hidden child, or anything faded out) is not a place a pointer can hit
|
|
209
|
+
if (sink && item.rid && opacity * local.opacity > 0) {
|
|
210
|
+
const rid = sink.prefix + item.rid
|
|
211
|
+
if (sink.hits.wants(rid)) sink.hits.add(rid, inner.entries, multiply(sink.matrix, multiply(matrix, local.matrix)))
|
|
212
|
+
}
|
|
213
|
+
for (let k = 0; k < inner.operations.length; k++) operations.push(inner.operations[k])
|
|
214
|
+
for (let k = 0; k < inner.entries.length; k++) entries.push({ holder: inner.entries[k].holder, matrix: multiply(local.matrix, inner.entries[k].matrix) })
|
|
215
|
+
} else if (item.kind === 'trim') {
|
|
216
|
+
trimEntries(entries, scalar(item.start(frame)), scalar(item.end(frame)), scalar(item.offset(frame)), item.together)
|
|
217
|
+
} else if (item.kind === 'merge') {
|
|
218
|
+
entries = entries.length > 1 ? [ { holder: { paths: [], merge: { mode: item.mode, parts: entries } }, matrix: IDENTITY } ] : entries
|
|
219
|
+
} else if (item.kind === 'paint') {
|
|
220
|
+
operations.push({ paint: item.paint, entries: entries.slice(), matrix, opacity })
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return { entries, operations }
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Trims every holder above, in its own space. Together, the holders' paths
|
|
228
|
+
* are trimmed as one list and each holder keeps its own share.
|
|
229
|
+
*
|
|
230
|
+
* @param {Entry[]} entries
|
|
231
|
+
* @param {number} start
|
|
232
|
+
* @param {number} end
|
|
233
|
+
* @param {number} offset
|
|
234
|
+
* @param {boolean} together
|
|
235
|
+
*/
|
|
236
|
+
function trimEntries(entries, start, end, offset, together) {
|
|
237
|
+
/** @type {Holder[]} */
|
|
238
|
+
const holders = []
|
|
239
|
+
for (let n = 0; n < entries.length; n++) {
|
|
240
|
+
const holder = entries[n].holder
|
|
241
|
+
if (!holder.merge) holders.push(holder)
|
|
242
|
+
else for (let k = 0; k < holder.merge.parts.length; k++) holders.push(holder.merge.parts[k].holder)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const all = []
|
|
246
|
+
const owners = []
|
|
247
|
+
for (let n = 0; n < holders.length; n++) {
|
|
248
|
+
for (let k = 0; k < holders[n].paths.length; k++) {
|
|
249
|
+
all.push(holders[n].paths[k])
|
|
250
|
+
owners.push(n)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const kept = trimEach(all, start, end, offset, together)
|
|
255
|
+
for (let n = 0; n < holders.length; n++) holders[n].paths = []
|
|
256
|
+
for (let k = 0; k < kept.length; k++) {
|
|
257
|
+
for (let j = 0; j < kept[k].length; j++) holders[owners[k]].paths.push(kept[k][j])
|
|
258
|
+
}
|
|
259
|
+
}
|