@px-petals/roku.motion-web 1.0.0 → 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 (77) hide show
  1. package/bands/bands.js +3 -1
  2. package/bundle/bundle.js +56 -0
  3. package/list/list.js +12 -1
  4. package/lottie/lottie.layers.js +64 -11
  5. package/lottie/lottie.paint.js +30 -0
  6. package/lottie/lottie.paths.js +19 -4
  7. package/lottie/lottie.props.js +18 -8
  8. package/lottie/lottie.shapes.js +2 -1
  9. package/mixer/mixer.js +31 -8
  10. package/mixer/mixer.mix.js +17 -0
  11. package/motion/motion.convert.js +32 -1
  12. package/motion/motion.flow.js +20 -2
  13. package/motion/motion.machine.js +28 -13
  14. package/motion/motion.values.js +13 -9
  15. package/motion.js +196 -20
  16. package/package.json +10 -16
  17. package/player/player.js +25 -5
  18. package/readme.md +78 -3
  19. package/script/luau/lib/base/base.js +202 -0
  20. package/script/luau/lib/base/base.table.js +189 -0
  21. package/script/luau/lib/lib.args.js +152 -0
  22. package/script/luau/lib/lib.js +23 -0
  23. package/script/luau/lib/math/math.bit32.js +144 -0
  24. package/script/luau/lib/math/math.js +288 -0
  25. package/script/luau/lib/math/math.vector.js +125 -0
  26. package/script/luau/lib/string/string.format.js +143 -0
  27. package/script/luau/lib/string/string.js +160 -0
  28. package/script/luau/lib/string/string.pattern.js +505 -0
  29. package/script/luau/load/load.js +362 -0
  30. package/script/luau/load/load.ops.js +79 -0
  31. package/script/luau/luau.convert.js +259 -0
  32. package/script/luau/luau.js +132 -0
  33. package/script/luau/luau.md +111 -0
  34. package/script/luau/vm/vm.js +167 -0
  35. package/script/luau/vm/vm.meta.js +401 -0
  36. package/script/luau/vm/vm.num.js +236 -0
  37. package/script/luau/vm/vm.run.js +842 -0
  38. package/script/luau/vm/vm.value.js +406 -0
  39. package/script/play/play.js +199 -0
  40. package/script/play/play.objects.js +295 -0
  41. package/script/play/play.scope.js +91 -0
  42. package/script/rive/api/api.statics.js +31 -0
  43. package/script/rive/api/api.type.js +188 -0
  44. package/script/rive/api/artboard/artboard.js +89 -0
  45. package/script/rive/api/assets/assets.js +72 -0
  46. package/script/rive/api/data/data.value.js +99 -0
  47. package/script/rive/api/draw/draw.contours.js +163 -0
  48. package/script/rive/api/draw/draw.measure.js +196 -0
  49. package/script/rive/api/draw/draw.paint.js +235 -0
  50. package/script/rive/api/draw/draw.path.js +153 -0
  51. package/script/rive/api/draw/draw.renderer.js +117 -0
  52. package/script/rive/api/math/math.color.js +96 -0
  53. package/script/rive/api/math/math.mat2d.js +180 -0
  54. package/script/rive/api/math/math.vector.js +121 -0
  55. package/script/rive/api/media/media.audio.js +113 -0
  56. package/script/rive/api/media/media.draw.js +73 -0
  57. package/script/rive/api/media/media.image.js +61 -0
  58. package/script/rive/api/media/media.mesh.js +50 -0
  59. package/script/rive/api/pointer/pointer.event.js +64 -0
  60. package/script/rive/api/pointer/pointer.route.js +69 -0
  61. package/script/rive/api/text/text.font.js +129 -0
  62. package/script/rive/api/text/text.js +278 -0
  63. package/script/rive/api/text/text.layout.js +259 -0
  64. package/script/rive/context/context.js +76 -0
  65. package/script/rive/context/context.memory.js +196 -0
  66. package/script/rive/context/context.viewmodel.js +302 -0
  67. package/script/rive/context/lists/lists.js +160 -0
  68. package/script/rive/context/lists/lists.values.js +281 -0
  69. package/script/rive/host/host.globals.js +44 -0
  70. package/script/rive/host/host.js +533 -0
  71. package/script/rive/host/host.node.js +45 -0
  72. package/script/rive/rive.js +23 -0
  73. package/script/rive/rive.md +180 -0
  74. package/script/script.js +11 -0
  75. package/skin/skin.deform.js +514 -0
  76. package/skin/skin.ik.js +260 -0
  77. package/skin/skin.js +297 -0
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Rive's `Mat2D`: a 2D affine matrix with fields `xx xy yx yy tx ty` (also
3
+ * indexed 1 to 6 in that order), where a point maps to
4
+ * `(xx * x + yx * y + tx, xy * x + yy * y + ty)`. These six numbers are
5
+ * Lottie's `[a, b, c, d, tx, ty]` in order. `a * b` applies `b` first.
6
+ */
7
+
8
+ import { LuaTable, NativeFunction, Vector, fail } from '../../../luau/vm/vm.value.js'
9
+ import { defineType, num, optNum, badArg, wrapped } from '../api.type.js'
10
+
11
+ /** The field names, in index order */
12
+ const NAMES = [ 'xx', 'xy', 'yx', 'yy', 'tx', 'ty' ]
13
+
14
+ /** @typedef {number[]} Matrix [xx, xy, yx, yy, tx, ty] */
15
+
16
+ /** @returns {Matrix} */
17
+ export function identity() {
18
+ return [ 1, 0, 0, 1, 0, 0 ]
19
+ }
20
+
21
+ /**
22
+ * The matrix that applies `b` first, then `a`.
23
+ *
24
+ * @param {Matrix} a
25
+ * @param {Matrix} b
26
+ * @returns {Matrix}
27
+ */
28
+ export function multiply(a, b) {
29
+ return [
30
+ a[0] * b[0] + a[2] * b[1],
31
+ a[1] * b[0] + a[3] * b[1],
32
+ a[0] * b[2] + a[2] * b[3],
33
+ a[1] * b[2] + a[3] * b[3],
34
+ a[0] * b[4] + a[2] * b[5] + a[4],
35
+ a[1] * b[4] + a[3] * b[5] + a[5],
36
+ ]
37
+ }
38
+
39
+ /**
40
+ * @param {Matrix} m
41
+ * @returns {Matrix | null} the inverse, or null when there is none
42
+ */
43
+ export function invert(m) {
44
+ const det = m[0] * m[3] - m[1] * m[2]
45
+ if (det === 0 || det !== det) return null
46
+ const id = 1 / det
47
+ return [
48
+ m[3] * id,
49
+ -m[1] * id,
50
+ -m[2] * id,
51
+ m[0] * id,
52
+ (m[2] * m[5] - m[3] * m[4]) * id,
53
+ (m[1] * m[4] - m[0] * m[5]) * id,
54
+ ]
55
+ }
56
+
57
+ /**
58
+ * @param {Matrix} m
59
+ * @param {number} x
60
+ * @param {number} y
61
+ * @returns {number[]} [x, y]
62
+ */
63
+ export function apply(m, x, y) {
64
+ return [ m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5] ]
65
+ }
66
+
67
+ /** @type {Record<string, { get: (h: Matrix) => any, set: (h: Matrix, v: any) => void }>} */
68
+ const fields = Object.create(null)
69
+ NAMES.forEach(function (name, i) {
70
+ fields[name] = {
71
+ get: (h) => h[i],
72
+ set: (h, v) => {
73
+ if (typeof v !== 'number') throw fail('Mat2D.' + name + ' must be a number')
74
+ h[i] = v
75
+ },
76
+ }
77
+ })
78
+
79
+ export const Mat2D = defineType('Mat2D', {
80
+ fields,
81
+ methods: {
82
+ invert: (h) => {
83
+ const inv = invert(h)
84
+ return inv ? Mat2D.wrap(inv) : undefined
85
+ },
86
+ isIdentity: (h) => h[0] === 1 && h[1] === 0 && h[2] === 0 && h[3] === 1 && h[4] === 0 && h[5] === 0,
87
+ },
88
+ index: (h, key) => (typeof key === 'number' && key >= 1 && key <= 6 && key === Math.floor(key) ? h[key - 1] : undefined),
89
+ newindex: (h, key, value) => {
90
+ if (typeof key !== 'number' || key < 1 || key > 6 || typeof value !== 'number') throw fail('invalid Mat2D index')
91
+ h[key - 1] = value
92
+ },
93
+ meta: {
94
+ __mul: (vm, args) => {
95
+ const a = Mat2D.unwrap(args[0])
96
+ if (a === undefined) throw badArg(0, 'Mat2D.__mul', 'Mat2D', args)
97
+ const b = args[1]
98
+ if (b instanceof Vector) {
99
+ const p = apply(a, b.x, b.y)
100
+ return new Vector(p[0], p[1], b.z)
101
+ }
102
+ const m = Mat2D.unwrap(b)
103
+ if (m === undefined) throw badArg(1, 'Mat2D.__mul', 'Vector or Mat2D', args)
104
+ return Mat2D.wrap(multiply(a, m))
105
+ },
106
+ __eq: (vm, args) => {
107
+ const a = Mat2D.unwrap(args[0])
108
+ const b = Mat2D.unwrap(args[1])
109
+ if (a === undefined || b === undefined) return false
110
+ for (let i = 0; i < 6; i++) if (a[i] !== b[i]) return false
111
+ return true
112
+ },
113
+ __tostring: (vm, args) => 'Mat2D(' + Mat2D.unwrap(args[0]).join(', ') + ')',
114
+ },
115
+ })
116
+
117
+ /**
118
+ * @param {any[]} args
119
+ * @param {number} i first of two numbers or one vector
120
+ * @param {string} fname
121
+ * @param {boolean} [single] a lone number stands for both axes
122
+ * @returns {number[]} [x, y]
123
+ */
124
+ function pair(args, i, fname, single) {
125
+ const v = args[i]
126
+ if (v instanceof Vector) return [ v.x, v.y ]
127
+ const x = num(args, i, fname)
128
+ return [ x, single && args[i + 1] === undefined ? x : optNum(args, i + 1, fname, 0) ]
129
+ }
130
+
131
+ /** @type {Record<string, (args: any[]) => any>} */
132
+ const STATICS = {
133
+ identity: () => Mat2D.wrap(identity()),
134
+ values: (a) => Mat2D.wrap([ num(a, 0, 'values'), num(a, 1, 'values'), num(a, 2, 'values'), num(a, 3, 'values'), num(a, 4, 'values'), num(a, 5, 'values') ]),
135
+ withRotation: (a) => {
136
+ const r = num(a, 0, 'withRotation')
137
+ const c = Math.cos(r)
138
+ const s = Math.sin(r)
139
+ return Mat2D.wrap([ c, s, -s, c, 0, 0 ])
140
+ },
141
+ withTranslation: (a) => {
142
+ const t = pair(a, 0, 'withTranslation')
143
+ return Mat2D.wrap([ 1, 0, 0, 1, t[0], t[1] ])
144
+ },
145
+ withScale: (a) => {
146
+ const s = pair(a, 0, 'withScale', true)
147
+ return Mat2D.wrap([ s[0], 0, 0, s[1], 0, 0 ])
148
+ },
149
+ withScaleAndTranslation: (a) => {
150
+ if (a[0] instanceof Vector) {
151
+ const s = pair(a, 0, 'withScaleAndTranslation')
152
+ const t = pair(a, 1, 'withScaleAndTranslation')
153
+ return Mat2D.wrap([ s[0], 0, 0, s[1], t[0], t[1] ])
154
+ }
155
+ return Mat2D.wrap([ num(a, 0, 'withScaleAndTranslation'), 0, 0, num(a, 1, 'withScaleAndTranslation'), num(a, 2, 'withScaleAndTranslation'), num(a, 3, 'withScaleAndTranslation') ])
156
+ },
157
+ invert: (a) => {
158
+ const out = wrapped(a, 0, Mat2D, 'invert')
159
+ const inv = invert(wrapped(a, 1, Mat2D, 'invert'))
160
+ if (!inv) return false
161
+ for (let i = 0; i < 6; i++) out[i] = inv[i]
162
+ return true
163
+ },
164
+ }
165
+
166
+ /**
167
+ * Installs the `Mat2D` global.
168
+ *
169
+ * @param {import('../../../luau/vm/vm.js').VM} vm
170
+ */
171
+ export function installMat2D(vm) {
172
+ const table = new LuaTable()
173
+ for (const name in STATICS) {
174
+ const fn = STATICS[name]
175
+ table.set(name, new NativeFunction('Mat2D.' + name, function (vm2, args) {
176
+ return fn(args)
177
+ }))
178
+ }
179
+ vm.globals.set('Mat2D', table)
180
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Rive's `Vector`: Luau's own vector value (x, y, z), made with `Vector.xy`
3
+ * and friends. Its methods are the same functions as the static ones, since
4
+ * a method call passes the vector first (`v:lerp(w, t)` is `Vector.lerp(v, w, t)`).
5
+ */
6
+
7
+ import { LuaTable, NativeFunction, Vector } from '../../../luau/vm/vm.value.js'
8
+ import { badArg, num, optNum } from '../api.type.js'
9
+
10
+ /**
11
+ * @param {any[]} args
12
+ * @param {number} i
13
+ * @param {string} fname
14
+ * @returns {Vector}
15
+ */
16
+ export function vec(args, i, fname) {
17
+ const v = args[i]
18
+ if (!(v instanceof Vector)) throw badArg(i, fname, 'Vector', args)
19
+ return v
20
+ }
21
+
22
+ /** @param {Vector} v */
23
+ function length(v) {
24
+ return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
25
+ }
26
+
27
+ /** @type {Record<string, (args: any[]) => any>} */
28
+ const OPS = {
29
+ length: (a) => length(vec(a, 0, 'length')),
30
+ lengthSquared: (a) => {
31
+ const v = vec(a, 0, 'lengthSquared')
32
+ return v.x * v.x + v.y * v.y + v.z * v.z
33
+ },
34
+ normalized: (a) => {
35
+ const v = vec(a, 0, 'normalized')
36
+ const l = length(v)
37
+ return l === 0 ? new Vector(0, 0, 0) : new Vector(v.x / l, v.y / l, v.z / l)
38
+ },
39
+ distance: (a) => {
40
+ const p = vec(a, 0, 'distance')
41
+ const q = vec(a, 1, 'distance')
42
+ return length(new Vector(p.x - q.x, p.y - q.y, p.z - q.z))
43
+ },
44
+ distanceSquared: (a) => {
45
+ const p = vec(a, 0, 'distanceSquared')
46
+ const q = vec(a, 1, 'distanceSquared')
47
+ return (p.x - q.x) * (p.x - q.x) + (p.y - q.y) * (p.y - q.y) + (p.z - q.z) * (p.z - q.z)
48
+ },
49
+ dot: (a) => {
50
+ const p = vec(a, 0, 'dot')
51
+ const q = vec(a, 1, 'dot')
52
+ return p.x * q.x + p.y * q.y + p.z * q.z
53
+ },
54
+ lerp: (a) => {
55
+ const p = vec(a, 0, 'lerp')
56
+ const q = vec(a, 1, 'lerp')
57
+ const t = num(a, 2, 'lerp')
58
+ return new Vector(p.x + (q.x - p.x) * t, p.y + (q.y - p.y) * t, p.z + (q.z - p.z) * t)
59
+ },
60
+ }
61
+
62
+ /** @type {Record<string, (args: any[]) => any>} */
63
+ const STATICS = {
64
+ xy: (a) => new Vector(num(a, 0, 'xy'), num(a, 1, 'xy'), 0),
65
+ xyz: (a) => new Vector(num(a, 0, 'xyz'), num(a, 1, 'xyz'), num(a, 2, 'xyz')),
66
+ origin: () => new Vector(0, 0, 0),
67
+ cross: (a) => {
68
+ const p = vec(a, 0, 'cross')
69
+ const q = vec(a, 1, 'cross')
70
+ return p.x * q.y - p.y * q.x
71
+ },
72
+ cross3: (a) => {
73
+ const p = vec(a, 0, 'cross3')
74
+ const q = vec(a, 1, 'cross3')
75
+ return new Vector(p.y * q.z - p.z * q.y, p.z * q.x - p.x * q.z, p.x * q.y - p.y * q.x)
76
+ },
77
+ scaleAndAdd: (a) => {
78
+ const p = vec(a, 0, 'scaleAndAdd')
79
+ const q = vec(a, 1, 'scaleAndAdd')
80
+ const s = optNum(a, 2, 'scaleAndAdd', 1)
81
+ return new Vector(p.x + q.x * s, p.y + q.y * s, p.z + q.z * s)
82
+ },
83
+ scaleAndSub: (a) => {
84
+ const p = vec(a, 0, 'scaleAndSub')
85
+ const q = vec(a, 1, 'scaleAndSub')
86
+ const s = optNum(a, 2, 'scaleAndSub', 1)
87
+ return new Vector(p.x - q.x * s, p.y - q.y * s, p.z - q.z * s)
88
+ },
89
+ }
90
+
91
+ /**
92
+ * Installs the `Vector` global and the methods vectors share.
93
+ *
94
+ * @param {import('../../../luau/vm/vm.js').VM} vm
95
+ */
96
+ export function installVector(vm) {
97
+ const statics = new LuaTable()
98
+ const methods = new LuaTable()
99
+ for (const name in STATICS) statics.set(name, native('Vector.' + name, STATICS[name]))
100
+ for (const name in OPS) {
101
+ statics.set(name, native('Vector.' + name, OPS[name]))
102
+ methods.set(name, native('Vector.' + name, OPS[name]))
103
+ }
104
+ vm.globals.set('Vector', statics)
105
+
106
+ const meta = vm.vectorMeta || new LuaTable()
107
+ meta.set('__index', methods)
108
+ vm.vectorMeta = meta
109
+ }
110
+
111
+ /**
112
+ * @param {string} name
113
+ * @param {(args: any[]) => any} fn
114
+ * @returns {NativeFunction}
115
+ */
116
+ function native(name, fn) {
117
+ return new NativeFunction(name, function (vm, args) {
118
+ return fn(args)
119
+ })
120
+ }
121
+
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Script audio, recorded rather than played: `AudioSource` (an audio asset,
3
+ * from `context:audio(name)`), the `Audio` global that starts it, and the
4
+ * `AudioSound` that stands for one playing. Every start, stop, pause, resume
5
+ * and seek is appended to the host's list as
6
+ * `{ kind: 'audio', action, asset, volume, time }` for the player to map to
7
+ * its own audio events (`host.audio()` takes the list).
8
+ */
9
+
10
+ import { badArg, defineType, num, optNum, wrapped } from '../api.type.js'
11
+ import { statics } from '../api.statics.js'
12
+
13
+ /**
14
+ * @typedef {object} AudioEvent
15
+ * @property {'audio'} kind
16
+ * @property {'play' | 'stop' | 'pause' | 'resume' | 'seek'} action
17
+ * @property {string} asset
18
+ * @property {number} volume 0 to 1
19
+ * @property {number} time the sound's position in seconds when it happened
20
+ * @property {number} [fade] a stop's fade, in PCM frames
21
+ */
22
+
23
+ /**
24
+ * @typedef {object} SoundData
25
+ * @property {string} name
26
+ * @property {number} volume
27
+ * @property {number} time
28
+ * @property {boolean} done
29
+ * @property {boolean} paused
30
+ * @property {AudioEvent[]} sink
31
+ */
32
+
33
+ export const AudioSource = defineType('AudioSource', {
34
+ fields: {
35
+ duration: { get: (h) => h.duration },
36
+ },
37
+ })
38
+
39
+ /**
40
+ * @param {SoundData} s
41
+ * @param {AudioEvent['action']} action
42
+ * @param {number} [fade]
43
+ */
44
+ function note(s, action, fade) {
45
+ /** @type {AudioEvent} */
46
+ const e = { kind: 'audio', action, asset: s.name, volume: s.volume, time: s.time }
47
+ if (fade !== undefined) e.fade = fade
48
+ s.sink.push(e)
49
+ }
50
+
51
+ export const AudioSound = defineType('AudioSound', {
52
+ fields: {
53
+ volume: { get: (s) => s.volume, set: (s, v) => { s.volume = typeof v === 'number' ? v : s.volume } },
54
+ },
55
+ methods: {
56
+ play: (s) => {
57
+ s.done = false
58
+ s.paused = false
59
+ note(s, 'play')
60
+ },
61
+ stop: (s, args) => {
62
+ s.done = true
63
+ note(s, 'stop', optNum(args, 1, 'stop', 0))
64
+ },
65
+ pause: (s) => {
66
+ s.paused = true
67
+ note(s, 'pause')
68
+ },
69
+ resume: (s) => {
70
+ s.paused = false
71
+ note(s, 'resume')
72
+ },
73
+ seek: (s, args) => {
74
+ if (s.done) return false
75
+ s.time = num(args, 1, 'seek')
76
+ note(s, 'seek')
77
+ return true
78
+ },
79
+ seekFrame: (s, args) => {
80
+ if (s.done) return false
81
+ s.time = num(args, 1, 'seekFrame') / 48000
82
+ note(s, 'seek')
83
+ return true
84
+ },
85
+ completed: (s) => s.done,
86
+ time: (s) => s.time,
87
+ timeFrame: (s) => Math.round(s.time * 48000),
88
+ },
89
+ })
90
+
91
+ /**
92
+ * Installs the `Audio` global; its sounds report to `sink`.
93
+ *
94
+ * @param {import('../../../luau/vm/vm.js').VM} vm
95
+ * @param {AudioEvent[]} sink
96
+ */
97
+ export function installAudio(vm, sink) {
98
+ /**
99
+ * `Audio:play(source, volume?)` or `Audio.play(source, volume?)`.
100
+ *
101
+ * @param {any[]} args
102
+ */
103
+ const play = (args) => {
104
+ const at = AudioSource.is(args[0]) ? 0 : 1
105
+ if (!AudioSource.is(args[at])) throw badArg(at, 'play', 'AudioSource', args)
106
+ const source = wrapped(args, at, AudioSource, 'play')
107
+ /** @type {SoundData} */
108
+ const s = { name: source.name, volume: optNum(args, at + 1, 'play', 1), time: 0, done: false, paused: false, sink }
109
+ note(s, 'play')
110
+ return AudioSound.wrap(s)
111
+ }
112
+ vm.globals.set('Audio', statics('Audio', { play }))
113
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * What `Renderer` records for images: `drawImage` and `drawImageMesh`, and
3
+ * the stamp every non-path record carries (the matrix, clips, opacity and
4
+ * colour modulation in force).
5
+ */
6
+
7
+ import { num, text, wrapped } from '../api.type.js'
8
+ import { Image, ImageSampler } from './media.image.js'
9
+ import { TriangleBuffer, VertexBuffer } from './media.mesh.js'
10
+
11
+ /**
12
+ * @typedef {object} ImageDrawing
13
+ * @property {'image'} kind
14
+ * @property {string} asset
15
+ * @property {number} width
16
+ * @property {number} height
17
+ * @property {number[]} transform [xx, xy, yx, yy, tx, ty]; shared, never changed
18
+ * @property {number[][][]} clips the clips in force
19
+ * @property {number} opacity 0 to 1: `modulateOpacity` times the call's own
20
+ * @property {number} color the colour modulation, ARGB
21
+ * @property {string} blendMode
22
+ * @property {import('./media.image.js').SamplerData} sampler
23
+ * @property {{ vertices: number[], uvs: number[], indices: number[] } | null} mesh a mesh's points, UVs and triangle indices
24
+ */
25
+
26
+ /**
27
+ * The state a drawing is made under, as the fields every record shares.
28
+ *
29
+ * @param {import('../draw/draw.renderer.js').Recorder} r
30
+ * @param {string} kind
31
+ * @returns {{ kind: string, transform: number[], clips: any[], opacity: number, color: number }}
32
+ */
33
+ export function stamp(r, kind) {
34
+ const s = r.state
35
+ return { kind, transform: s.m, clips: s.clips, opacity: s.opacity, color: s.color }
36
+ }
37
+
38
+ /**
39
+ * @param {import('../draw/draw.renderer.js').Recorder} r
40
+ * @param {any[]} args image, sampler, then what each call takes
41
+ * @param {number} at where blendMode and opacity start
42
+ * @param {boolean} mesh
43
+ */
44
+ function record(r, args, at, mesh) {
45
+ const fname = mesh ? 'drawImageMesh' : 'drawImage'
46
+ const image = wrapped(args, 1, Image, fname)
47
+ const sampler = wrapped(args, 2, ImageSampler, fname)
48
+ const rec = /** @type {any} */ (stamp(r, 'image'))
49
+ rec.asset = image.name
50
+ rec.width = image.width
51
+ rec.height = image.height
52
+ rec.blendMode = args[at] === undefined ? 'srcOver' : text(args, at, fname)
53
+ rec.opacity = r.state.opacity * (args[at + 1] === undefined ? 1 : num(args, at + 1, fname))
54
+ rec.sampler = sampler
55
+ rec.mesh = mesh
56
+ ? {
57
+ vertices: wrapped(args, 3, VertexBuffer, fname).data.slice(),
58
+ uvs: wrapped(args, 4, VertexBuffer, fname).data.slice(),
59
+ indices: wrapped(args, 5, TriangleBuffer, fname).data.slice(),
60
+ }
61
+ : null
62
+ r.drawings.push(rec)
63
+ }
64
+
65
+ /** `drawImage(image, sampler, blendMode, opacity)` @param {any} r @param {any[]} args */
66
+ export function drawImage(r, args) {
67
+ record(r, args, 3, false)
68
+ }
69
+
70
+ /** `drawImageMesh(image, sampler, vertices, uvs, triangles, blendMode, opacity)` @param {any} r @param {any[]} args */
71
+ export function drawImageMesh(r, args) {
72
+ record(r, args, 6, true)
73
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * `Image` (a decoded picture the script holds, by asset name) and
3
+ * `ImageSampler` (how it is read when drawn). A player gives a script images
4
+ * through `context:image(name)`, an image input, or an image view-model
5
+ * property; the host only names the asset and knows its size.
6
+ */
7
+
8
+ import { defineType, text } from '../api.type.js'
9
+ import { statics } from '../api.statics.js'
10
+
11
+ /**
12
+ * @typedef {object} ImageData
13
+ * @property {string} name the asset
14
+ * @property {number} width 0 until the player knows it
15
+ * @property {number} height
16
+ */
17
+
18
+ /**
19
+ * @typedef {object} SamplerData
20
+ * @property {string} wrapX `clamp`, `repeat` or `mirror`
21
+ * @property {string} wrapY
22
+ * @property {string} filter `bilinear` or `nearest`
23
+ */
24
+
25
+ export const Image = defineType('Image', {
26
+ fields: {
27
+ width: { get: (h) => h.width },
28
+ height: { get: (h) => h.height },
29
+ },
30
+ })
31
+
32
+ export const ImageSampler = defineType('ImageSampler', {
33
+ fields: {
34
+ wrapX: { get: (h) => h.wrapX },
35
+ wrapY: { get: (h) => h.wrapY },
36
+ filter: { get: (h) => h.filter },
37
+ },
38
+ })
39
+
40
+ /**
41
+ * @param {any[]} a wrapX, wrapY, filter
42
+ * @returns {any}
43
+ */
44
+ function newSampler(a) {
45
+ /** @type {SamplerData} */
46
+ const h = {
47
+ wrapX: a[0] === undefined ? 'clamp' : text(a, 0, 'ImageSampler'),
48
+ wrapY: a[1] === undefined ? 'clamp' : text(a, 1, 'ImageSampler'),
49
+ filter: a[2] === undefined ? 'bilinear' : text(a, 2, 'ImageSampler'),
50
+ }
51
+ return ImageSampler.wrap(h)
52
+ }
53
+
54
+ /**
55
+ * Installs the `ImageSampler` global.
56
+ *
57
+ * @param {import('../../../luau/vm/vm.js').VM} vm
58
+ */
59
+ export function installImage(vm) {
60
+ vm.globals.set('ImageSampler', statics('ImageSampler', { new: newSampler }, 'new'))
61
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * `VertexBuffer` and `TriangleBuffer`: the points and the triangles of an
3
+ * image mesh, as flat number lists the recorder copies at the draw.
4
+ */
5
+
6
+ import { Vector } from '../../../luau/vm/vm.value.js'
7
+ import { badArg, defineType, num } from '../api.type.js'
8
+ import { statics } from '../api.statics.js'
9
+
10
+ export const VertexBuffer = defineType('VertexBuffer', {
11
+ fields: {
12
+ length: { get: (h) => h.data.length / 2 },
13
+ },
14
+ methods: {
15
+ add: (h, args) => {
16
+ for (let i = 1; i < args.length; i++) {
17
+ const v = args[i]
18
+ if (!(v instanceof Vector)) throw badArg(i, 'add', 'Vector', args)
19
+ h.data.push(v.x, v.y)
20
+ }
21
+ },
22
+ reset: (h) => {
23
+ h.data.length = 0
24
+ },
25
+ },
26
+ })
27
+
28
+ export const TriangleBuffer = defineType('TriangleBuffer', {
29
+ fields: {
30
+ length: { get: (h) => h.data.length / 3 },
31
+ },
32
+ methods: {
33
+ add: (h, args) => {
34
+ h.data.push(num(args, 1, 'add'), num(args, 2, 'add'), num(args, 3, 'add'))
35
+ },
36
+ reset: (h) => {
37
+ h.data.length = 0
38
+ },
39
+ },
40
+ })
41
+
42
+ /**
43
+ * Installs the `VertexBuffer` and `TriangleBuffer` globals.
44
+ *
45
+ * @param {import('../../../luau/vm/vm.js').VM} vm
46
+ */
47
+ export function installMesh(vm) {
48
+ vm.globals.set('VertexBuffer', statics('VertexBuffer', { new: () => VertexBuffer.wrap({ data: [] }) }, 'new'))
49
+ vm.globals.set('TriangleBuffer', statics('TriangleBuffer', { new: () => TriangleBuffer.wrap({ data: [] }) }, 'new'))
50
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * `PointerEvent` as a scripted node receives it in `pointerDown`,
3
+ * `pointerMove`, `pointerUp` and `pointerExit`: `position` (the script's local
4
+ * space), `id`, `type`, `previousPosition`, `timeStamp`, and `hit(translucent?)`
5
+ * which claims the event. `PointerEvent.new(id, position)` makes one, to
6
+ * forward to a nested artboard.
7
+ */
8
+
9
+ import { Vector } from '../../../luau/vm/vm.value.js'
10
+ import { badArg, defineType, num } from '../api.type.js'
11
+ import { statics } from '../api.statics.js'
12
+
13
+ /**
14
+ * @typedef {object} PointerData
15
+ * @property {number} id
16
+ * @property {Vector} position
17
+ * @property {Vector} previous
18
+ * @property {string} type
19
+ * @property {number} time
20
+ * @property {boolean} hit the script called `hit`
21
+ * @property {boolean} translucent and let the event through
22
+ */
23
+
24
+ export const PointerEvent = defineType('PointerEvent', {
25
+ fields: {
26
+ position: { get: (e) => e.position },
27
+ previousPosition: { get: (e) => e.previous },
28
+ id: { get: (e) => e.id },
29
+ type: { get: (e) => e.type },
30
+ timeStamp: { get: (e) => e.time },
31
+ },
32
+ methods: {
33
+ hit: (e, args) => {
34
+ e.hit = true
35
+ e.translucent = args[1] === true
36
+ },
37
+ },
38
+ })
39
+
40
+ /**
41
+ * @param {number} id
42
+ * @param {Vector} position
43
+ * @param {Vector} previous
44
+ * @param {string} type
45
+ * @param {number} time
46
+ * @returns {PointerData}
47
+ */
48
+ export function pointerData(id, position, previous, type, time) {
49
+ return { id, position, previous, type, time, hit: false, translucent: false }
50
+ }
51
+
52
+ /**
53
+ * Installs the `PointerEvent` global.
54
+ *
55
+ * @param {import('../../../luau/vm/vm.js').VM} vm
56
+ */
57
+ export function installPointer(vm) {
58
+ vm.globals.set('PointerEvent', statics('PointerEvent', {
59
+ new: (a) => {
60
+ if (!(a[1] instanceof Vector)) throw badArg(1, 'new', 'Vector', a)
61
+ return PointerEvent.wrap(pointerData(num(a, 0, 'new'), a[1], a[1], 'pointerMove', 0))
62
+ },
63
+ }))
64
+ }