@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.
- package/bands/bands.js +3 -1
- package/bundle/bundle.js +56 -0
- package/list/list.js +12 -1
- package/lottie/lottie.layers.js +64 -11
- package/lottie/lottie.paint.js +30 -0
- package/lottie/lottie.paths.js +19 -4
- package/lottie/lottie.props.js +18 -8
- package/lottie/lottie.shapes.js +2 -1
- package/mixer/mixer.js +31 -8
- package/mixer/mixer.mix.js +17 -0
- package/motion/motion.convert.js +32 -1
- package/motion/motion.flow.js +20 -2
- package/motion/motion.machine.js +28 -13
- package/motion/motion.values.js +13 -9
- package/motion.js +196 -20
- package/package.json +10 -16
- package/player/player.js +25 -5
- package/readme.md +78 -3
- 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
|
@@ -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
|
+
}
|