@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
package/mixer/mixer.js ADDED
@@ -0,0 +1,307 @@
1
+ /**
2
+ * The pose mixer: draws several animations of one artboard at once, each at
3
+ * its own time and weight, as one picture.
4
+ *
5
+ * rive2lottie writes every animation of an artboard as a Lottie with the
6
+ * same layers and shapes, and tags each property with `rid`, the Rive object
7
+ * and property it came from, and `ky` when that animation keys it. The mixer
8
+ * takes one of them (the longest) as the picture's structure, and gives each
9
+ * keyed property a mixing evaluator in place of its keyframes. For a set of
10
+ * poses, `{ animation, time, weight }`, that evaluator reads the property in
11
+ * each pose's animation at the pose's time and blends the values in order,
12
+ * as Rive applies animations: each lerps the value so far toward its own by
13
+ * its weight. A pose whose animation doesn't key the property leaves it
14
+ * alone. The value so far starts from what the property held after the
15
+ * previous frame, as Rive's artboard keeps what it last applied: a property
16
+ * no active animation keys stays where it was, and a blend whose weights
17
+ * don't add up to one settles over a few frames. A pair of axes (position)
18
+ * keyed on one axis only is mixed on that axis only. What isn't tagged (a nested artboard's own animation) follows the
19
+ * first pose's time; text overlays are mixed by the player (`mixOverlays`).
20
+ */
21
+
22
+ import { createAnimation } from '../lottie/lottie.js'
23
+ import { evaluator } from '../lottie/lottie.props.js'
24
+ import { mixValues } from './mixer.mix.js'
25
+
26
+ /** @import { Animation } from '../lottie/lottie.js' */
27
+ /** @import { Evaluator } from '../lottie/lottie.props.js' */
28
+ /** @import { Matrix } from '../lottie/lottie.paths.js' */
29
+
30
+ /**
31
+ * One animation shown at a time, with a weight.
32
+ * @typedef {object} Pose
33
+ * @property {string} animation an animation id
34
+ * @property {number} time seconds into it
35
+ * @property {number} weight 0 to 1
36
+ */
37
+
38
+ /** What an animation does to one property: its value at a frame, whether it keys the property, and, for a pair of axes with one keyed, which; `picks` for a Solo option's visibility, which an animation sets whole, never blends */
39
+ /** @typedef {{ at: Evaluator, keyed: boolean, axes: number[] | null, picks: boolean }} Source */
40
+
41
+ /** The `rid` of a Solo option's visibility: `<object id>.solo` */
42
+ const SOLO = /\.solo$/
43
+
44
+ /** A pose, prepared for the evaluators: where its animation's sources are, and how far its frame is from the drawn one */
45
+ /** @typedef {{ animation: string, sources: Record<string, Source>, offset: number, weight: number }} Prepared */
46
+
47
+ /**
48
+ * What the mixing evaluators share: the poses of the draw, and each property's value after the previous frame.
49
+ * @typedef {object} Current
50
+ * @property {Prepared[]} poses
51
+ * @property {Record<string, any>} last by `rid`, what each held after the previous frame
52
+ * @property {Record<string, any>} next by `rid`, what each holds in this one
53
+ * @property {number} frame the frame `next` belongs to
54
+ * @property {number} passes how many times a draw applies the poses
55
+ */
56
+
57
+ /** No slots: a source reads its keyframes, and the drawn animation's own slots go over the result */
58
+ const NO_SLOTS = {}
59
+
60
+ /**
61
+ * @typedef {object} Mixer
62
+ * @property {Animation} animation the mixed picture; its `slots` are live
63
+ * @property {string} carrier the animation whose structure it has
64
+ * @property {(ctx: CanvasRenderingContext2D, poses: Pose[], matrix: Matrix, frame?: number, passes?: number) => void} draw `frame` counts the stage's steps: drawing the same frame again leaves each property where it was; `passes` applies the poses that many times, for a stage that has stopped but must settle
65
+ * @property {(read: (animation: string) => any, base: any) => Evaluator} mixed a value each animation gives on its own (a slot the player fills, such as a skin's path), mixed over the poses being drawn: each pose whose animation gives one lerps toward it by its weight
66
+ */
67
+
68
+ /**
69
+ * @param {Record<string, any>} lotties parsed Lottie JSON by animation id; the longest is changed in place
70
+ * @param {object} [options]
71
+ * @param {string} [options.base] where images resolve from
72
+ * @param {() => Record<string, string>} [options.families] the bundle's fonts by name, as registered
73
+ * @returns {Mixer}
74
+ */
75
+ export function createMixer(lotties, options) {
76
+ const carrier = longest(lotties)
77
+ /** @type {Record<string, Record<string, Source>>} */
78
+ const sources = {}
79
+ for (const id in lotties) sources[id] = collect(lotties[id])
80
+
81
+ /** @type {Current} */
82
+ const current = { poses: [], last: {}, next: {}, frame: -1, passes: 1 }
83
+ install(lotties[carrier], sources, carrier, current)
84
+ const animation = createAnimation(lotties[carrier], options)
85
+
86
+ return {
87
+ animation,
88
+ carrier,
89
+ draw(ctx, poses, matrix, frame, passes) {
90
+ current.passes = passes || 1
91
+ // a new frame: what the last one left is where this one starts
92
+ if (frame === undefined || frame !== current.frame) {
93
+ for (const rid in current.next) current.last[rid] = current.next[rid]
94
+ current.next = {}
95
+ current.frame = frame === undefined ? -1 : frame
96
+ }
97
+ animation.draw(ctx, prepare(lotties, sources, poses, current, carrier), matrix)
98
+ },
99
+ mixed(read, base) {
100
+ return (frame) => {
101
+ let value = typeof base === 'function' ? base(frame) : base
102
+ for (let n = 0; n < current.poses.length; n++) {
103
+ const pose = current.poses[n]
104
+ const own = read(pose.animation)
105
+ if (own === undefined) continue
106
+ value = mixValues(value, typeof own === 'function' ? own(frame + pose.offset) : own, pose.weight)
107
+ }
108
+ return value
109
+ }
110
+ },
111
+ }
112
+ }
113
+
114
+ /**
115
+ * The animation with the longest timeline, in seconds: any pose's time fits it.
116
+ *
117
+ * @param {Record<string, any>} lotties
118
+ * @returns {string}
119
+ */
120
+ function longest(lotties) {
121
+ let best = ''
122
+ let bestSeconds = -1
123
+ for (const id in lotties) {
124
+ const lottie = lotties[id]
125
+ const seconds = ((lottie.op || 0) - (lottie.ip || 0)) / (lottie.fr || 30)
126
+ if (seconds > bestSeconds) {
127
+ best = id
128
+ bestSeconds = seconds
129
+ }
130
+ }
131
+ return best
132
+ }
133
+
134
+ /**
135
+ * Every tagged property of a Lottie, by its `rid`; the first of a repeated one wins.
136
+ *
137
+ * @param {any} lottie
138
+ * @returns {Record<string, Source>}
139
+ */
140
+ function collect(lottie) {
141
+ /** @type {Record<string, Source>} */
142
+ const found = {}
143
+ visit(lottie, (property) => {
144
+ if (found[property.rid]) return
145
+ found[property.rid] = { at: evaluator(property, NO_SLOTS), keyed: property.ky === 1, axes: Array.isArray(property.kc) ? property.kc : null, picks: SOLO.test(property.rid) }
146
+ })
147
+ return found
148
+ }
149
+
150
+ /**
151
+ * Calls back for every tagged property under a node, in assets as in layers.
152
+ *
153
+ * @param {any} node
154
+ * @param {(property: any) => void} found
155
+ */
156
+ function visit(node, found) {
157
+ if (node === null || typeof node !== 'object') return
158
+ if (Array.isArray(node)) {
159
+ for (let n = 0; n < node.length; n++) visit(node[n], found)
160
+ return
161
+ }
162
+ if (typeof node.rid === 'string' && 'k' in node) found(node)
163
+ // a property's `k` holds its values; a gradient's `g.k` is a property itself
164
+ const isProperty = 'a' in node || typeof node.rid === 'string'
165
+ for (const key in node) {
166
+ if ((key === 'k' && isProperty) || key === 'rid') continue
167
+ visit(node[key], found)
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Gives the carrier's keyed properties their mixing evaluators.
173
+ *
174
+ * @param {any} lottie
175
+ * @param {Record<string, Record<string, Source>>} sources
176
+ * @param {string} carrier
177
+ * @param {Current} current
178
+ */
179
+ function install(lottie, sources, carrier, current) {
180
+ /** @type {Record<string, any>} */
181
+ const bases = {}
182
+ /** @type {Record<string, boolean>} */
183
+ const isKeyed = {}
184
+ for (const id in sources) {
185
+ for (const rid in sources[id]) {
186
+ if (sources[id][rid].keyed) isKeyed[rid] = true
187
+ }
188
+ }
189
+ for (const rid in isKeyed) bases[rid] = baseOf(rid, sources, carrier, lottie)
190
+
191
+ visit(lottie, (property) => {
192
+ if (isKeyed[property.rid]) property.mx = mixing(property.rid, bases[property.rid], current)
193
+ })
194
+ }
195
+
196
+ /**
197
+ * A property's value with no animation on it: where one animation holds it
198
+ * still, that value; where all key it, the carrier's first. A pair of axes
199
+ * takes each axis from an animation that holds that axis still.
200
+ *
201
+ * @param {string} rid
202
+ * @param {Record<string, Record<string, Source>>} sources
203
+ * @param {string} carrier
204
+ * @param {any} lottie
205
+ * @returns {any}
206
+ */
207
+ function baseOf(rid, sources, carrier, lottie) {
208
+ for (const id in sources) {
209
+ const source = sources[id][rid]
210
+ if (source && !source.keyed) return source.at(0)
211
+ }
212
+ // a property the carrier does not tag starts from the first animation that does
213
+ let own = sources[carrier][rid]
214
+ for (const id in sources) if (!own) own = sources[id][rid]
215
+ const first = own.at(lottie.ip || 0)
216
+ if (!Array.isArray(first)) return first
217
+
218
+ const result = first.slice()
219
+ for (let axis = 0; axis < result.length; axis++) {
220
+ for (const id in sources) {
221
+ const source = sources[id][rid]
222
+ if (!source || !source.axes || source.axes[axis]) continue
223
+ const still = source.at(0)
224
+ if (Array.isArray(still) && typeof still[axis] === 'number') result[axis] = still[axis]
225
+ break
226
+ }
227
+ }
228
+ return result
229
+ }
230
+
231
+ /**
232
+ * Mixes only the keyed axes of a vector.
233
+ *
234
+ * @param {any} a the value so far
235
+ * @param {any} b the value being mixed in
236
+ * @param {number} t b's weight
237
+ * @param {number[]} axes 1 for each axis b moves
238
+ * @returns {any}
239
+ */
240
+ function mixAxes(a, b, t, axes) {
241
+ if (t <= 0 || !Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return mixValues(a, b, t)
242
+ const result = a.slice()
243
+ for (let n = 0; n < result.length; n++) {
244
+ if (axes[n] && typeof a[n] === 'number' && typeof b[n] === 'number') result[n] = a[n] + (b[n] - a[n]) * Math.min(1, t)
245
+ }
246
+ return result
247
+ }
248
+
249
+ /**
250
+ * @param {string} rid
251
+ * @param {any} base
252
+ * @param {Current} current
253
+ * @returns {Evaluator}
254
+ */
255
+ function mixing(rid, base, current) {
256
+ return (frame) => {
257
+ const poses = current.poses
258
+ let value = rid in current.last ? current.last[rid] : base
259
+ for (let pass = 0; pass < current.passes; pass++) {
260
+ for (let n = 0; n < poses.length; n++) {
261
+ const pose = poses[n]
262
+ const source = pose.sources[rid]
263
+ if (!source || !source.keyed) continue
264
+
265
+ const sample = source.at(frame + pose.offset)
266
+ // a Solo's choice is an id, which Rive sets from any animation applying it, the last one winning
267
+ if (source.picks) value = pose.weight > 0 ? sample : value
268
+ else value = source.axes ? mixAxes(value, sample, pose.weight, source.axes) : mixValues(value, sample, pose.weight)
269
+ }
270
+ }
271
+ current.next[rid] = value
272
+ return value
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Readies the poses for a draw.
278
+ *
279
+ * @param {Record<string, any>} lotties
280
+ * @param {Record<string, Record<string, Source>>} sources
281
+ * @param {Pose[]} poses
282
+ * @param {Current} current
283
+ * @param {string} carrier
284
+ * @returns {number} the carrier's frame to draw
285
+ */
286
+ function prepare(lotties, sources, poses, current, carrier) {
287
+ const structure = lotties[carrier]
288
+ const first = structure.ip || 0
289
+ const last = (structure.op || 0) - 0.001
290
+ current.poses = []
291
+ let drawn = first
292
+ let isFirst = true
293
+ for (let n = 0; n < poses.length; n++) {
294
+ const pose = poses[n]
295
+ const lottie = lotties[pose.animation]
296
+ if (!lottie) continue
297
+
298
+ if (isFirst) {
299
+ // the first pose sets the frame that what isn't tagged follows
300
+ drawn = Math.min(Math.max(first + pose.time * (structure.fr || 30), first), last)
301
+ isFirst = false
302
+ }
303
+ const frame = (lottie.ip || 0) + pose.time * (lottie.fr || 30)
304
+ current.poses.push({ animation: pose.animation, sources: sources[pose.animation], offset: frame - drawn, weight: pose.weight })
305
+ }
306
+ return drawn
307
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Mixing two values of one Lottie property: numbers and vectors (positions,
3
+ * scales, colours) blend component by component, and paths blend vertex by
4
+ * vertex when they have the same vertices. Anything else can't be blended,
5
+ * so the heavier of the two wins.
6
+ */
7
+
8
+ /**
9
+ * @param {any} a the value so far
10
+ * @param {any} b the value being mixed in
11
+ * @param {number} t b's weight, 0 to 1: 0 is all `a`, 1 is all `b`
12
+ * @returns {any}
13
+ */
14
+ export function mixValues(a, b, t) {
15
+ if (t <= 0 || b === undefined) return a
16
+ if (t >= 1 || a === undefined) return b
17
+
18
+ if (typeof a === 'number' && typeof b === 'number') return a + (b - a) * t
19
+
20
+ // keyed values arrive as [v], static ones as v: a number and a one-element vector mix as a vector
21
+ const left = typeof a === 'number' ? [ a ] : a
22
+ const right = typeof b === 'number' ? [ b ] : b
23
+ if (isVector(left) && isVector(right) && left.length === right.length) return mixVectors(left, right, t)
24
+
25
+ const leftPath = pathOf(a)
26
+ const rightPath = pathOf(b)
27
+ if (leftPath && rightPath && sameVertices(leftPath, rightPath)) {
28
+ const mixed = mixPaths(leftPath, rightPath, t)
29
+ return Array.isArray(a) || Array.isArray(b) ? [ mixed ] : mixed
30
+ }
31
+
32
+ return t >= 0.5 ? b : a
33
+ }
34
+
35
+ /** @param {any} value */
36
+ function isVector(value) {
37
+ if (!Array.isArray(value)) return false
38
+ for (let n = 0; n < value.length; n++) if (typeof value[n] !== 'number') return false
39
+ return true
40
+ }
41
+
42
+ /**
43
+ * @param {number[]} a
44
+ * @param {number[]} b
45
+ * @param {number} t
46
+ */
47
+ function mixVectors(a, b, t) {
48
+ const result = []
49
+ for (let n = 0; n < a.length; n++) result.push(a[n] + (b[n] - a[n]) * t)
50
+ return result
51
+ }
52
+
53
+ /**
54
+ * A path value is `{ c, v, i, o }`, or that in a one-element array.
55
+ *
56
+ * @param {any} value
57
+ * @returns {any} the path, or null
58
+ */
59
+ function pathOf(value) {
60
+ const path = Array.isArray(value) ? value[0] : value
61
+ return path && typeof path === 'object' && Array.isArray(path.v) ? path : null
62
+ }
63
+
64
+ /**
65
+ * @param {any} a
66
+ * @param {any} b
67
+ */
68
+ function sameVertices(a, b) {
69
+ return a.c === b.c && a.v.length === b.v.length && a.i.length === b.i.length && a.o.length === b.o.length
70
+ }
71
+
72
+ /**
73
+ * @param {any} a
74
+ * @param {any} b
75
+ * @param {number} t
76
+ */
77
+ function mixPaths(a, b, t) {
78
+ return { c: a.c, v: mixPoints(a.v, b.v, t), i: mixPoints(a.i, b.i, t), o: mixPoints(a.o, b.o, t) }
79
+ }
80
+
81
+ /**
82
+ * @param {number[][]} a
83
+ * @param {number[][]} b
84
+ * @param {number} t
85
+ */
86
+ function mixPoints(a, b, t) {
87
+ const points = []
88
+ for (let n = 0; n < a.length; n++) points.push(mixVectors(a[n], b[n], t))
89
+ return points
90
+ }
91
+
92
+ /**
93
+ * How much a pose counts in the value the poses settle on: its weight, times
94
+ * what each pose after it that keys the value too leaves of it.
95
+ *
96
+ * @param {Array<{ start: number, weight: number }>} poses
97
+ * @param {number} at the pose's index
98
+ * @param {number[]} keying the segments that key the value
99
+ * @returns {number}
100
+ */
101
+ export function settledWeight(poses, at, keying) {
102
+ let weight = poses[at].weight
103
+ for (let n = at + 1; n < poses.length && weight > 0; n++) {
104
+ if (keying.indexOf(poses[n].start) >= 0) weight *= 1 - Math.min(1, poses[n].weight)
105
+ }
106
+ return weight
107
+ }