@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.
Files changed (99) hide show
  1. package/LICENSE +201 -0
  2. package/audio/audio.cues.js +27 -0
  3. package/audio/audio.js +259 -0
  4. package/bands/bands.js +392 -0
  5. package/bundle/bundle.fonts.js +55 -0
  6. package/bundle/bundle.js +131 -0
  7. package/focus/focus.direction.js +46 -0
  8. package/focus/focus.dom.js +158 -0
  9. package/focus/focus.js +277 -0
  10. package/focus/focus.tree.js +146 -0
  11. package/hit/hit.geometry.js +113 -0
  12. package/hit/hit.js +129 -0
  13. package/list/list.flex.js +181 -0
  14. package/list/list.items.js +80 -0
  15. package/list/list.js +518 -0
  16. package/lottie/lottie.js +124 -0
  17. package/lottie/lottie.layers.js +416 -0
  18. package/lottie/lottie.paint.js +238 -0
  19. package/lottie/lottie.paths.js +474 -0
  20. package/lottie/lottie.props.js +265 -0
  21. package/lottie/lottie.shapes.js +259 -0
  22. package/mixer/mixer.js +307 -0
  23. package/mixer/mixer.mix.js +107 -0
  24. package/mixer/mixer.stage.js +431 -0
  25. package/motion/motion.convert.js +404 -0
  26. package/motion/motion.flow.js +421 -0
  27. package/motion/motion.keys.js +150 -0
  28. package/motion/motion.machine.js +382 -0
  29. package/motion/motion.nested.js +345 -0
  30. package/motion/motion.values.js +368 -0
  31. package/motion.js +1155 -0
  32. package/package.json +47 -4
  33. package/player/overlay/overlay.js +325 -0
  34. package/player/overlay/overlay.layout.js +208 -0
  35. package/player/player.fit.js +101 -0
  36. package/player/player.js +421 -0
  37. package/player/player.raster.js +127 -0
  38. package/pointer/pointer.js +292 -0
  39. package/readme.md +345 -0
  40. package/script/luau/lib/base/base.js +202 -0
  41. package/script/luau/lib/base/base.table.js +189 -0
  42. package/script/luau/lib/lib.args.js +152 -0
  43. package/script/luau/lib/lib.js +23 -0
  44. package/script/luau/lib/math/math.bit32.js +144 -0
  45. package/script/luau/lib/math/math.js +288 -0
  46. package/script/luau/lib/math/math.vector.js +125 -0
  47. package/script/luau/lib/string/string.format.js +143 -0
  48. package/script/luau/lib/string/string.js +160 -0
  49. package/script/luau/lib/string/string.pattern.js +505 -0
  50. package/script/luau/load/load.js +362 -0
  51. package/script/luau/load/load.ops.js +79 -0
  52. package/script/luau/luau.convert.js +259 -0
  53. package/script/luau/luau.js +132 -0
  54. package/script/luau/luau.md +111 -0
  55. package/script/luau/vm/vm.js +167 -0
  56. package/script/luau/vm/vm.meta.js +401 -0
  57. package/script/luau/vm/vm.num.js +236 -0
  58. package/script/luau/vm/vm.run.js +842 -0
  59. package/script/luau/vm/vm.value.js +406 -0
  60. package/script/play/play.js +199 -0
  61. package/script/play/play.objects.js +295 -0
  62. package/script/play/play.scope.js +91 -0
  63. package/script/rive/api/api.statics.js +31 -0
  64. package/script/rive/api/api.type.js +188 -0
  65. package/script/rive/api/artboard/artboard.js +89 -0
  66. package/script/rive/api/assets/assets.js +72 -0
  67. package/script/rive/api/data/data.value.js +99 -0
  68. package/script/rive/api/draw/draw.contours.js +163 -0
  69. package/script/rive/api/draw/draw.measure.js +196 -0
  70. package/script/rive/api/draw/draw.paint.js +235 -0
  71. package/script/rive/api/draw/draw.path.js +153 -0
  72. package/script/rive/api/draw/draw.renderer.js +117 -0
  73. package/script/rive/api/math/math.color.js +96 -0
  74. package/script/rive/api/math/math.mat2d.js +180 -0
  75. package/script/rive/api/math/math.vector.js +121 -0
  76. package/script/rive/api/media/media.audio.js +113 -0
  77. package/script/rive/api/media/media.draw.js +73 -0
  78. package/script/rive/api/media/media.image.js +61 -0
  79. package/script/rive/api/media/media.mesh.js +50 -0
  80. package/script/rive/api/pointer/pointer.event.js +64 -0
  81. package/script/rive/api/pointer/pointer.route.js +69 -0
  82. package/script/rive/api/text/text.font.js +129 -0
  83. package/script/rive/api/text/text.js +278 -0
  84. package/script/rive/api/text/text.layout.js +259 -0
  85. package/script/rive/context/context.js +76 -0
  86. package/script/rive/context/context.memory.js +196 -0
  87. package/script/rive/context/context.viewmodel.js +302 -0
  88. package/script/rive/context/lists/lists.js +160 -0
  89. package/script/rive/context/lists/lists.values.js +281 -0
  90. package/script/rive/host/host.globals.js +44 -0
  91. package/script/rive/host/host.js +533 -0
  92. package/script/rive/host/host.node.js +45 -0
  93. package/script/rive/rive.js +23 -0
  94. package/script/rive/rive.md +180 -0
  95. package/script/script.js +11 -0
  96. package/skin/skin.deform.js +514 -0
  97. package/skin/skin.ik.js +260 -0
  98. package/skin/skin.js +297 -0
  99. package/README.md +0 -3
@@ -0,0 +1,431 @@
1
+ /**
2
+ * The stage: what is playing, per layer, as poses for the mixer. It knows no
3
+ * state machine, only what the machine tells it: a layer enters a state,
4
+ * with a cross-fade or without. It keeps each state's clock and says, for a
5
+ * set of input values, which animations show at what time and weight.
6
+ *
7
+ * - A layer holds a list of runs, oldest first. A run is a state being
8
+ * played. Entering a state with a cross-fade adds a run whose weight rises
9
+ * from 0 to 1 over the fade, while the outgoing run plays on; entering one
10
+ * without replaces them. A run that reaches full weight drops the runs
11
+ * before it, and a run entered during a fade stacks on the last, so a
12
+ * cross-fade interrupted by another continues from where it was.
13
+ * - A blend state is a run with several animations: a 1D blend weights the
14
+ * two animations either side of its input's value, a direct blend weights
15
+ * each by its own input (0 to 100).
16
+ * - A state's `speed` scales its clock, and its `mode` plays its timelines
17
+ * backwards (`Reverse`, `ReverseBounce`). A ping-pong timeline is already
18
+ * written out there and back, so `Bounce` plays it forward.
19
+ * - Layers apply in order, so a later layer overrides what it keys.
20
+ */
21
+
22
+ import { convertValue } from '../motion/motion.convert.js'
23
+
24
+ /** @import { Pose } from './mixer.js' */
25
+
26
+ /** Runs one layer keeps: an endless chain of interrupted fades is cut here */
27
+ const MAX_RUNS = 4
28
+
29
+ /**
30
+ * One state being played.
31
+ * @typedef {object} Run
32
+ * @property {any} state
33
+ * @property {number} time seconds since it was entered, unwrapped
34
+ * @property {number} ramp its weight, 0 to 1
35
+ * @property {number[] | null} ease the curve the ramp follows from `fade`'s share of `fadeFor`: a cubic's `[x1, y1, x2, y2]`; null for linear
36
+ * @property {number} fade seconds the ramp has taken
37
+ * @property {number} fadeFor seconds the ramp takes
38
+ * @property {boolean} finished a state that does not loop has reached its end
39
+ * @property {number} from where the last step began, in seconds of the state's clock; -1 before the first, so frame 0 sounds
40
+ */
41
+
42
+ /**
43
+ * @param {Record<string, { frames: number, fps: number }>} animations a bundle's animations
44
+ */
45
+ export function createStage(animations) {
46
+ /** @type {{ runs: Run[] }[]} */
47
+ let layers = []
48
+ let loopsAll = false
49
+ let frames = 0
50
+
51
+ /**
52
+ * An animation's length in seconds.
53
+ * @param {string} id
54
+ */
55
+ function lengthOf(id) {
56
+ const entry = animations[id]
57
+ return entry && entry.fps > 0 ? entry.frames / entry.fps : 0
58
+ }
59
+
60
+ /**
61
+ * A state's timeline: its animation's, or a blend's longest.
62
+ * @param {any} state
63
+ */
64
+ function stateLength(state) {
65
+ if (!state.blend) return lengthOf(state.animation)
66
+ let longest = 0
67
+ for (let n = 0; n < state.blend.animations.length; n++) longest = Math.max(longest, lengthOf(state.blend.animations[n].animation))
68
+ return longest
69
+ }
70
+
71
+ /**
72
+ * A state loops when it says so, or the host asked for every state to.
73
+ * @param {any} state
74
+ */
75
+ function loops(state) {
76
+ return loopsAll || state.loop === true
77
+ }
78
+
79
+ /**
80
+ * @param {number} layer
81
+ * @param {any} state a machine state: an animation, or a blend of several
82
+ * @param {number} fadeMs how long the layer's current state fades into it
83
+ * @param {number[]} [ease] the fade's cubic curve; linear without
84
+ */
85
+ function enter(layer, state, fadeMs, ease) {
86
+ if (!layers[layer]) layers[layer] = { runs: [] }
87
+ const target = layers[layer]
88
+
89
+ /** @type {Run} */
90
+ const run = { state, time: 0, ramp: 1, ease: ease && ease.length === 4 ? ease : null, fade: 0, fadeFor: 0, finished: false, from: -1 }
91
+ if (fadeMs > 0 && target.runs.length) {
92
+ run.ramp = 0
93
+ run.fadeFor = fadeMs / 1000
94
+ target.runs.push(run)
95
+ if (target.runs.length > MAX_RUNS) target.runs.shift()
96
+ } else {
97
+ target.runs = [ run ]
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Plays time forward.
103
+ *
104
+ * @param {number} seconds
105
+ * @returns {number[]} the layers whose state, which does not loop, reached its end
106
+ */
107
+ function advance(seconds) {
108
+ frames += 1
109
+ /** @type {number[]} */
110
+ const done = []
111
+ for (let n = 0; n < layers.length; n++) {
112
+ const layer = layers[n]
113
+ if (!layer) continue
114
+
115
+ for (let r = 0; r < layer.runs.length; r++) {
116
+ const run = layer.runs[r]
117
+ run.from = run.time === 0 && run.from === -1 ? -1 : run.time
118
+ run.time += seconds * speedOf(run.state)
119
+ if (run.ramp < 1) {
120
+ run.fade += seconds
121
+ run.ramp = run.fadeFor > 0 ? eased(Math.min(1, run.fade / run.fadeFor), run.ease) : 1
122
+ }
123
+ }
124
+
125
+ // the newest run at full weight is all that shows: the ones before it go
126
+ for (let r = layer.runs.length - 1; r > 0; r--) {
127
+ if (layer.runs[r].ramp >= 1) {
128
+ layer.runs = layer.runs.slice(r)
129
+ break
130
+ }
131
+ }
132
+
133
+ const newest = layer.runs[layer.runs.length - 1]
134
+ if (!newest.finished && !loops(newest.state) && newest.time >= stateLength(newest.state)) {
135
+ newest.finished = true
136
+ done.push(n)
137
+ }
138
+ }
139
+ return done
140
+ }
141
+
142
+ /**
143
+ * How fast a state plays its timeline, as its `speed` says (1 without).
144
+ * @param {any} state
145
+ */
146
+ function speedOf(state) {
147
+ return typeof state.speed === 'number' && state.speed > 0 ? state.speed : 1
148
+ }
149
+
150
+ /**
151
+ * An animation's time at a run's time: wrapped if it loops, held a frame
152
+ * before its end if it doesn't (the last frame, as the plain player holds it).
153
+ *
154
+ * @param {number} time
155
+ * @param {string} id
156
+ * @param {boolean} isLooping
157
+ * @param {string} [mode] the state's `Reverse` or `ReverseBounce`; forward without
158
+ * @returns {number}
159
+ */
160
+ function timeIn(time, id, isLooping, mode) {
161
+ const length = lengthOf(id)
162
+ if (length <= 0) return 0
163
+ // a ping-pong timeline is written out forward then back, so bouncing is playing it through, and bouncing in reverse is playing it backwards
164
+ if (mode === 'Reverse' || mode === 'ReverseBounce') return isLooping ? length - (time % length) : Math.max(0, length - time)
165
+ if (isLooping) return time % length
166
+ const entry = animations[id]
167
+ return time < length ? time : Math.max(0, length - 1 / entry.fps)
168
+ }
169
+
170
+ /**
171
+ * What shows now, in the order the mixer applies it.
172
+ *
173
+ * @param {Record<string, any>} values the machine's inputs, which blends read
174
+ * @returns {Pose[]}
175
+ */
176
+ function poses(values) {
177
+ /** @type {Pose[]} */
178
+ const result = []
179
+ for (let n = 0; n < layers.length; n++) {
180
+ const layer = layers[n]
181
+ if (!layer) continue
182
+ for (let r = 0; r < layer.runs.length; r++) {
183
+ const run = layer.runs[r]
184
+ if (run.state.blend) addBlend(result, run, values)
185
+ else result.push({ animation: run.state.animation, time: timeIn(run.time, run.state.animation, loops(run.state), run.state.mode), weight: run.ramp })
186
+ }
187
+ }
188
+ return result
189
+ }
190
+
191
+ /**
192
+ * @param {Pose[]} result
193
+ * @param {Run} run
194
+ * @param {Record<string, any>} values
195
+ */
196
+ function addBlend(result, run, values) {
197
+ const blend = run.state.blend
198
+ const weights = blend.type === 'Direct' ? directWeights(blend, values) : oneDimensionWeights(blend, values)
199
+ for (let n = 0; n < weights.length; n++) {
200
+ const entry = blend.animations[weights[n].index]
201
+ const weight = weights[n].weight * run.ramp
202
+ if (weight <= 0) continue
203
+ result.push({ animation: entry.animation, time: timeIn(run.time, entry.animation, loopsAll || entry.loop === true, run.state.mode), weight })
204
+ }
205
+ }
206
+
207
+ /**
208
+ * The stretch of each playing animation's own timeline the last step passed
209
+ * over, in seconds, for its audio cues: more than one where it looped, and
210
+ * where a state plays backwards, the stretch of the timeline as it is laid out.
211
+ *
212
+ * @param {Record<string, any>} values
213
+ * @returns {{ animation: string, from: number, to: number }[]}
214
+ */
215
+ function sweeps(values) {
216
+ /** @type {{ animation: string, from: number, to: number }[]} */
217
+ const result = []
218
+ for (let n = 0; n < layers.length; n++) {
219
+ const layer = layers[n]
220
+ if (!layer) continue
221
+ for (let r = 0; r < layer.runs.length; r++) {
222
+ const run = layer.runs[r]
223
+ const state = run.state
224
+ /** @type {{ id: string, isLooping: boolean }[]} */
225
+ const shown = []
226
+ if (!state.blend) shown.push({ id: state.animation, isLooping: loops(state) })
227
+ else {
228
+ const weights = state.blend.type === 'Direct' ? directWeights(state.blend, values) : oneDimensionWeights(state.blend, values)
229
+ for (let w = 0; w < weights.length; w++) {
230
+ const entry = state.blend.animations[weights[w].index]
231
+ if (weights[w].weight * run.ramp > 0) shown.push({ id: entry.animation, isLooping: loopsAll || entry.loop === true })
232
+ }
233
+ }
234
+ for (let s = 0; s < shown.length; s++) {
235
+ const length = lengthOf(shown[s].id)
236
+ const parts = segments(run.from, run.time, length, shown[s].isLooping)
237
+ for (let p = 0; p < parts.length; p++) {
238
+ const isReversed = state.mode === 'Reverse' || state.mode === 'ReverseBounce'
239
+ result.push(isReversed ? { animation: shown[s].id, from: length - parts[p][1], to: length - parts[p][0] } : { animation: shown[s].id, from: parts[p][0], to: parts[p][1] })
240
+ }
241
+ }
242
+ }
243
+ }
244
+ return result
245
+ }
246
+
247
+ /** The newest run of the first layer: what text overlays follow. */
248
+ function lead() {
249
+ const layer = layers[0]
250
+ if (!layer || !layer.runs.length) return null
251
+ const run = layer.runs[layer.runs.length - 1]
252
+ if (!run.state.blend) return { animation: run.state.animation, time: timeIn(run.time, run.state.animation, loops(run.state), run.state.mode) }
253
+ const first = run.state.blend.animations[0]
254
+ return first ? { animation: first.animation, time: timeIn(run.time, first.animation, loopsAll || first.loop === true, run.state.mode) } : null
255
+ }
256
+
257
+ return {
258
+ enter,
259
+ advance,
260
+ poses,
261
+ lead,
262
+ sweeps,
263
+
264
+ /** @returns {number} how many times time has moved on: the mixer's frame */
265
+ get frame() {
266
+ return frames
267
+ },
268
+
269
+ /**
270
+ * @param {number} layer
271
+ * @returns {number} ms the layer's current state has played, unwrapped
272
+ */
273
+ elapsedMs(layer) {
274
+ const target = layers[layer]
275
+ return target && target.runs.length ? target.runs[target.runs.length - 1].time * 1000 : 0
276
+ },
277
+
278
+ /** Whether nothing moves: every state has run out, and no fade is left. */
279
+ isIdle() {
280
+ for (let n = 0; n < layers.length; n++) {
281
+ const layer = layers[n]
282
+ if (!layer) continue
283
+ const newest = layer.runs[layer.runs.length - 1]
284
+ if (layer.runs.length > 1 || !newest.finished) return false
285
+ }
286
+ return true
287
+ },
288
+
289
+ /** Starts every layer's current state over, fades done. */
290
+ rewind() {
291
+ for (let n = 0; n < layers.length; n++) {
292
+ const layer = layers[n]
293
+ if (!layer || !layer.runs.length) continue
294
+ const newest = layer.runs[layer.runs.length - 1]
295
+ layer.runs = [ { state: newest.state, time: 0, ramp: 1, ease: null, fade: 0, fadeFor: 0, finished: false, from: -1 } ]
296
+ }
297
+ },
298
+
299
+ /** @param {boolean} on every state loops, as the `loop` control asks */
300
+ setLoops(on) {
301
+ loopsAll = on
302
+ },
303
+
304
+ /** Forgets every layer. */
305
+ clear() {
306
+ layers = []
307
+ },
308
+ }
309
+ }
310
+
311
+ /**
312
+ * The stretches of a timeline that a step from `from` to `to` (a state's
313
+ * unwrapped clock) passes over: one, or one per time it wrapped, each after
314
+ * the first from before frame 0 so that frame sounds again. A timeline that
315
+ * does not loop stops at its end.
316
+ *
317
+ * @param {number} from
318
+ * @param {number} to
319
+ * @param {number} length the timeline's, in seconds
320
+ * @param {boolean} isLooping
321
+ * @returns {[number, number][]}
322
+ */
323
+ export function segments(from, to, length, isLooping) {
324
+ if (length <= 0 || to <= from) return []
325
+ if (!isLooping) return from < length ? [ [ from, Math.min(to, length) ] ] : []
326
+
327
+ /** @type {[number, number][]} */
328
+ const parts = []
329
+ const first = Math.floor(Math.max(from, 0) / length)
330
+ let start = from
331
+ for (let wrap = first; wrap < first + 8; wrap++) {
332
+ const base = wrap * length
333
+ parts.push([ start < 0 ? start : start - base, Math.min(to, base + length) - base ])
334
+ if (to <= base + length) break
335
+ start = -1
336
+ }
337
+ return parts
338
+ }
339
+
340
+ /**
341
+ * How far a cross-fade is along its curve: a cubic Bezier from (0, 0) to
342
+ * (1, 1) with control points (x1, y1) and (x2, y2), read at time `t`, as
343
+ * Rive's cubic interpolator reads it. Without a curve, `t` itself.
344
+ *
345
+ * @param {number} t 0 to 1, how much of the fade's time has passed
346
+ * @param {number[]|null} curve [x1, y1, x2, y2]
347
+ * @returns {number}
348
+ */
349
+ export function eased(t, curve) {
350
+ if (!curve || t <= 0 || t >= 1) return t
351
+ const x1 = curve[0]
352
+ const x2 = curve[2]
353
+ // solve x(s) = t for the curve's parameter s, by bisection: x is rising for the control points a designer can draw
354
+ let low = 0
355
+ let high = 1
356
+ let s = t
357
+ for (let n = 0; n < 24; n++) {
358
+ s = (low + high) / 2
359
+ const x = bezier(s, x1, x2)
360
+ if (x < t) low = s
361
+ else high = s
362
+ }
363
+ return bezier(s, curve[1], curve[3])
364
+ }
365
+
366
+ /**
367
+ * One axis of a cubic Bezier from 0 to 1.
368
+ *
369
+ * @param {number} s
370
+ * @param {number} p1
371
+ * @param {number} p2
372
+ */
373
+ function bezier(s, p1, p2) {
374
+ const u = 1 - s
375
+ return 3 * u * u * s * p1 + 3 * u * s * s * p2 + s * s * s
376
+ }
377
+
378
+ /**
379
+ * A 1D blend: the animation at the input's value, or the two either side of
380
+ * it, weighted by how far along it is (the first by what is left, as Rive
381
+ * applies them; they lerp onto what the property held, so the pair settles
382
+ * a little off a plain average). A value outside the range
383
+ * plays the animation at that end.
384
+ *
385
+ * @param {any} blend
386
+ * @param {Record<string, any>} values
387
+ * @returns {{ index: number, weight: number }[]}
388
+ */
389
+ export function oneDimensionWeights(blend, values) {
390
+ const raw = Number(convertValue(blend.convert, values[blend.input]))
391
+ const x = raw === raw ? raw : 0
392
+
393
+ // by value, as the animations were authored: the order may not be sorted
394
+ const order = []
395
+ for (let n = 0; n < blend.animations.length; n++) order.push(n)
396
+ order.sort((a, b) => blend.animations[a].value - blend.animations[b].value)
397
+ if (!order.length) return []
398
+
399
+ const first = order[0]
400
+ const last = order[order.length - 1]
401
+ if (x <= blend.animations[first].value) return [ { index: first, weight: 1 } ]
402
+ if (x >= blend.animations[last].value) return [ { index: last, weight: 1 } ]
403
+
404
+ for (let n = 0; n < order.length - 1; n++) {
405
+ const from = blend.animations[order[n]]
406
+ const to = blend.animations[order[n + 1]]
407
+ if (x > to.value) continue
408
+ const span = to.value - from.value
409
+ return [ { index: order[n], weight: 1 - (span > 0 ? (x - from.value) / span : 1) }, { index: order[n + 1], weight: span > 0 ? (x - from.value) / span : 1 } ]
410
+ }
411
+ return [ { index: last, weight: 1 } ]
412
+ }
413
+
414
+ /**
415
+ * A direct blend: each animation weighted by its own input, 0 to 100, or by
416
+ * the fixed mix the file gives it.
417
+ *
418
+ * @param {any} blend
419
+ * @param {Record<string, any>} values
420
+ * @returns {{ index: number, weight: number }[]}
421
+ */
422
+ export function directWeights(blend, values) {
423
+ const weights = []
424
+ for (let n = 0; n < blend.animations.length; n++) {
425
+ const entry = blend.animations[n]
426
+ const raw = typeof entry.input === 'string' ? Number(convertValue(entry.convert, values[entry.input])) : entry.mix === undefined ? 100 : entry.mix
427
+ const percent = raw === raw ? raw : 0
428
+ weights.push({ index: n, weight: Math.min(1, Math.max(0, percent / 100)) })
429
+ }
430
+ return weights
431
+ }