@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,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rive's `Paint` (how a path is filled or stroked) and `Gradient`. A paint
|
|
3
|
+
* is mutable; the renderer copies it when a path is drawn, so a script can
|
|
4
|
+
* change one between draws.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { LuaTable, NativeFunction, Vector, fail } from '../../../luau/vm/vm.value.js'
|
|
8
|
+
import { decodeUtf8 } from '../../../luau/luau.convert.js'
|
|
9
|
+
import { defineType, badArg, num, wrapped } from '../api.type.js'
|
|
10
|
+
import { Mat2D } from '../math/math.mat2d.js'
|
|
11
|
+
import { channels } from '../math/math.color.js'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {object} Stop
|
|
15
|
+
* @property {number} position 0 to 1
|
|
16
|
+
* @property {number} color ARGB
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {object} GradientData
|
|
21
|
+
* @property {'linear' | 'radial'} kind
|
|
22
|
+
* @property {number[]} from [x, y]: the start of a linear gradient, the centre of a radial one
|
|
23
|
+
* @property {number[]} [to] [x, y]: the end of a linear one
|
|
24
|
+
* @property {number} [radius]
|
|
25
|
+
* @property {Stop[]} stops
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {object} PaintData
|
|
30
|
+
* @property {'fill' | 'stroke'} style
|
|
31
|
+
* @property {number} color ARGB
|
|
32
|
+
* @property {number} thickness
|
|
33
|
+
* @property {string} join miter, round, bevel
|
|
34
|
+
* @property {string} cap butt, round, square
|
|
35
|
+
* @property {string} blendMode
|
|
36
|
+
* @property {number} feather
|
|
37
|
+
* @property {GradientData | null} gradient
|
|
38
|
+
* @property {number[] | null} gradientTransform
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
const STYLES = [ 'fill', 'stroke' ]
|
|
42
|
+
const JOINS = [ 'miter', 'round', 'bevel' ]
|
|
43
|
+
const CAPS = [ 'butt', 'round', 'square' ]
|
|
44
|
+
const BLENDS = [ 'srcOver', 'additive', 'screen', 'overlay', 'darken', 'lighten', 'colorDodge', 'colorBurn', 'hardLight', 'softLight', 'difference', 'exclusion', 'multiply', 'hue', 'saturation', 'color', 'luminosity' ]
|
|
45
|
+
|
|
46
|
+
/** @returns {PaintData} */
|
|
47
|
+
export function defaultPaint() {
|
|
48
|
+
return { style: 'fill', color: 0xff000000, thickness: 1, join: 'miter', cap: 'butt', blendMode: 'srcOver', feather: 0, gradient: null, gradientTransform: null }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {string} name
|
|
53
|
+
* @param {string[]} allowed
|
|
54
|
+
* @returns {{ get: (h: any) => any, set: (h: any, v: any) => void }}
|
|
55
|
+
*/
|
|
56
|
+
function choice(name, allowed) {
|
|
57
|
+
return {
|
|
58
|
+
get: (h) => h[name],
|
|
59
|
+
set: (h, v) => {
|
|
60
|
+
const s = typeof v === 'string' ? decodeUtf8(v) : ''
|
|
61
|
+
if (allowed.indexOf(s) < 0) throw fail("invalid Paint." + name + ' (' + allowed.join(', ') + ' expected)')
|
|
62
|
+
h[name] = s
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param {string} name
|
|
69
|
+
* @returns {{ get: (h: any) => any, set: (h: any, v: any) => void }}
|
|
70
|
+
*/
|
|
71
|
+
function numeric(name) {
|
|
72
|
+
return {
|
|
73
|
+
get: (h) => h[name],
|
|
74
|
+
set: (h, v) => {
|
|
75
|
+
if (typeof v !== 'number') throw fail('Paint.' + name + ' must be a number')
|
|
76
|
+
h[name] = v
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const Gradient = defineType('Gradient', {})
|
|
82
|
+
|
|
83
|
+
export const Paint = defineType('Paint', {
|
|
84
|
+
fields: {
|
|
85
|
+
style: choice('style', STYLES),
|
|
86
|
+
join: choice('join', JOINS),
|
|
87
|
+
cap: choice('cap', CAPS),
|
|
88
|
+
blendMode: choice('blendMode', BLENDS),
|
|
89
|
+
color: numeric('color'),
|
|
90
|
+
thickness: numeric('thickness'),
|
|
91
|
+
feather: numeric('feather'),
|
|
92
|
+
gradient: {
|
|
93
|
+
get: (h) => (h.gradient ? Gradient.wrap(h.gradient) : undefined),
|
|
94
|
+
set: (h, v) => {
|
|
95
|
+
h.gradient = v === undefined ? null : wrapped([ v ], 0, Gradient, 'gradient')
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
gradientTransform: {
|
|
99
|
+
get: (h) => (h.gradientTransform ? Mat2D.wrap(h.gradientTransform.slice()) : undefined),
|
|
100
|
+
set: (h, v) => {
|
|
101
|
+
h.gradientTransform = v === undefined ? null : wrapped([ v ], 0, Mat2D, 'gradientTransform').slice()
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
methods: {
|
|
106
|
+
copy: (h, args) => applyDefinition(copyPaint(h), args[1]),
|
|
107
|
+
},
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @param {PaintData} h
|
|
112
|
+
* @returns {PaintData}
|
|
113
|
+
*/
|
|
114
|
+
function copyPaint(h) {
|
|
115
|
+
return { style: h.style, color: h.color, thickness: h.thickness, join: h.join, cap: h.cap, blendMode: h.blendMode, feather: h.feather, gradient: h.gradient, gradientTransform: h.gradientTransform }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Sets the fields a definition table names.
|
|
120
|
+
*
|
|
121
|
+
* @param {PaintData} h
|
|
122
|
+
* @param {any} def a table of fields, or nil
|
|
123
|
+
* @returns {any} the paint, wrapped
|
|
124
|
+
*/
|
|
125
|
+
function applyDefinition(h, def) {
|
|
126
|
+
const w = Paint.wrap(h)
|
|
127
|
+
if (def === undefined) return w
|
|
128
|
+
if (!(def instanceof LuaTable)) throw badArg(0, 'Paint.with', 'table', [ def ])
|
|
129
|
+
for (let pos = 0, next = def.advance(pos); next !== null; next = def.advance(pos)) {
|
|
130
|
+
pos = next
|
|
131
|
+
const key = typeof def.ck === 'string' ? def.ck : ''
|
|
132
|
+
if (key === '') continue
|
|
133
|
+
// the metatable's __newindex validates each field
|
|
134
|
+
const setter = /** @type {LuaTable} */ (Paint.meta).getStr('__newindex')
|
|
135
|
+
if (setter instanceof NativeFunction) setter.fn(null, [ w, key, def.cv ])
|
|
136
|
+
}
|
|
137
|
+
return w
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @param {any} t a table of `{ position, color }`
|
|
142
|
+
* @returns {Stop[]}
|
|
143
|
+
*/
|
|
144
|
+
function stopsOf(t) {
|
|
145
|
+
if (!(t instanceof LuaTable)) throw badArg(2, 'Gradient', 'table of stops', [ undefined, undefined, t ])
|
|
146
|
+
/** @type {Stop[]} */
|
|
147
|
+
const stops = []
|
|
148
|
+
for (let n = 1; n <= t.length(); n++) {
|
|
149
|
+
const s = t.get(n)
|
|
150
|
+
if (!(s instanceof LuaTable)) throw fail('Gradient stop ' + n + ' must be a table')
|
|
151
|
+
const position = s.getStr('position')
|
|
152
|
+
const color = s.getStr('color')
|
|
153
|
+
if (typeof position !== 'number' || typeof color !== 'number') throw fail('Gradient stop ' + n + ' needs a position and a color')
|
|
154
|
+
stops.push({ position, color })
|
|
155
|
+
}
|
|
156
|
+
return stops
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @param {any[]} args
|
|
161
|
+
* @param {number} i
|
|
162
|
+
* @param {string} fname
|
|
163
|
+
* @returns {number[]}
|
|
164
|
+
*/
|
|
165
|
+
function point(args, i, fname) {
|
|
166
|
+
const v = args[i]
|
|
167
|
+
if (!(v instanceof Vector)) throw badArg(i, fname, 'Vector', args)
|
|
168
|
+
return [ v.x, v.y ]
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** @type {Record<string, (args: any[]) => any>} */
|
|
172
|
+
const PAINT_STATICS = {
|
|
173
|
+
new: () => Paint.wrap(defaultPaint()),
|
|
174
|
+
with: (a) => applyDefinition(defaultPaint(), a[0]),
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** @type {Record<string, (args: any[]) => any>} */
|
|
178
|
+
const GRADIENT_STATICS = {
|
|
179
|
+
linear: (a) => Gradient.wrap({ kind: 'linear', from: point(a, 0, 'linear'), to: point(a, 1, 'linear'), stops: stopsOf(a[2]) }),
|
|
180
|
+
radial: (a) => Gradient.wrap({ kind: 'radial', from: point(a, 0, 'radial'), radius: num(a, 1, 'radial'), stops: stopsOf(a[2]) }),
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* @param {string} prefix
|
|
185
|
+
* @param {Record<string, (args: any[]) => any>} fns
|
|
186
|
+
* @returns {LuaTable}
|
|
187
|
+
*/
|
|
188
|
+
function table(prefix, fns) {
|
|
189
|
+
const t = new LuaTable()
|
|
190
|
+
for (const name in fns) {
|
|
191
|
+
const fn = fns[name]
|
|
192
|
+
t.set(name, new NativeFunction(prefix + '.' + name, function (vm, args) {
|
|
193
|
+
return fn(args)
|
|
194
|
+
}))
|
|
195
|
+
}
|
|
196
|
+
return t
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Installs the `Paint` and `Gradient` globals.
|
|
201
|
+
*
|
|
202
|
+
* @param {import('../../../luau/vm/vm.js').VM} vm
|
|
203
|
+
*/
|
|
204
|
+
export function installPaint(vm) {
|
|
205
|
+
vm.globals.set('Paint', table('Paint', PAINT_STATICS))
|
|
206
|
+
vm.globals.set('Gradient', table('Gradient', GRADIENT_STATICS))
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* A paint as the plain record a drawing carries.
|
|
211
|
+
*
|
|
212
|
+
* @param {PaintData} h
|
|
213
|
+
* @param {number} [modulate] a colour the renderer multiplies in (ARGB)
|
|
214
|
+
* @returns {{ style: string, color: number, rgba: number[], thickness: number, join: string, cap: string, blendMode: string, feather: number, gradient: GradientData | null, gradientTransform: number[] | null }}
|
|
215
|
+
*/
|
|
216
|
+
export function snapshotPaint(h, modulate) {
|
|
217
|
+
const ch = channels(h.color)
|
|
218
|
+
if (modulate !== undefined && modulate !== 0xffffffff) {
|
|
219
|
+
const m = channels(modulate)
|
|
220
|
+
for (let i = 0; i < 4; i++) ch[i] = Math.round((ch[i] * m[i]) / 255)
|
|
221
|
+
}
|
|
222
|
+
const color = ch[3] * 16777216 + ch[0] * 65536 + ch[1] * 256 + ch[2]
|
|
223
|
+
return {
|
|
224
|
+
style: h.style,
|
|
225
|
+
color,
|
|
226
|
+
rgba: [ ch[0] / 255, ch[1] / 255, ch[2] / 255, ch[3] / 255 ],
|
|
227
|
+
thickness: h.thickness,
|
|
228
|
+
join: h.join,
|
|
229
|
+
cap: h.cap,
|
|
230
|
+
blendMode: h.blendMode,
|
|
231
|
+
feather: h.feather,
|
|
232
|
+
gradient: h.gradient,
|
|
233
|
+
gradientTransform: h.gradientTransform ? h.gradientTransform.slice() : null,
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rive's `Path` (and the `PathData` a path effect receives, which is the same
|
|
3
|
+
* thing here): a list of drawing commands a script builds with `moveTo`,
|
|
4
|
+
* `lineTo`, `quadTo`, `cubicTo` and `close`. It reads as an array of
|
|
5
|
+
* `PathCommand`s too (`#path`, `path[i]`, `ipairs`), each a table with a `type`
|
|
6
|
+
* and its points as vectors, which the wrapper keeps in step with the commands.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { LuaTable, NativeFunction, Vector, fail } from '../../../luau/vm/vm.value.js'
|
|
10
|
+
import { Wrapped, defineType, wrapped } from '../api.type.js'
|
|
11
|
+
import { Mat2D, apply } from '../math/math.mat2d.js'
|
|
12
|
+
import { vec } from '../math/math.vector.js'
|
|
13
|
+
import { commandsToContours, contoursToCommands } from './draw.contours.js'
|
|
14
|
+
import { measureOf } from './draw.measure.js'
|
|
15
|
+
|
|
16
|
+
/** @import { Command, Contour } from './draw.contours.js' */
|
|
17
|
+
|
|
18
|
+
/** @typedef {{ cmds: Command[], wrapper: Wrapped | null }} PathData */
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {Command} cmd
|
|
22
|
+
* @returns {LuaTable} the `PathCommand` a script sees
|
|
23
|
+
*/
|
|
24
|
+
function commandTable(cmd) {
|
|
25
|
+
const t = new LuaTable()
|
|
26
|
+
t.set('type', cmd.t)
|
|
27
|
+
const points = []
|
|
28
|
+
for (let i = 0; i < cmd.p.length; i += 2) points.push(new Vector(cmd.p[i], cmd.p[i + 1], 0))
|
|
29
|
+
t.setRange(1, points, 0, points.length)
|
|
30
|
+
return t
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Appends a command to a path and its wrapper's array.
|
|
35
|
+
*
|
|
36
|
+
* @param {Wrapped} w
|
|
37
|
+
* @param {Command} cmd
|
|
38
|
+
*/
|
|
39
|
+
function push(w, cmd) {
|
|
40
|
+
w.host.cmds.push(cmd)
|
|
41
|
+
w.set(w.host.cmds.length, commandTable(cmd))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {any[]} args
|
|
46
|
+
* @param {number} first the index of the first Vector argument
|
|
47
|
+
* @param {number} n how many vectors
|
|
48
|
+
* @param {string} fname
|
|
49
|
+
* @returns {number[]} their x, y flattened
|
|
50
|
+
*/
|
|
51
|
+
function points(args, first, n, fname) {
|
|
52
|
+
const out = []
|
|
53
|
+
for (let k = 0; k < n; k++) {
|
|
54
|
+
const v = vec(args, first + k, fname)
|
|
55
|
+
out.push(v.x, v.y)
|
|
56
|
+
}
|
|
57
|
+
return out
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @param {any} dest the Path a script passed to `extract`
|
|
62
|
+
* @param {Command[]} cmds
|
|
63
|
+
*/
|
|
64
|
+
function append(dest, cmds) {
|
|
65
|
+
const w = dest instanceof Wrapped && Path.is(dest) ? dest : null
|
|
66
|
+
if (!w) throw fail("invalid argument #3 to 'extract' (Path expected)")
|
|
67
|
+
for (let n = 0; n < cmds.length; n++) push(w, cmds[n])
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The Lua type; `args[0]` is the wrapper in a method */
|
|
71
|
+
export const Path = defineType('Path', {
|
|
72
|
+
methods: {
|
|
73
|
+
moveTo: (h, args) => push(h.wrapper, { t: 'moveTo', p: points(args, 1, 1, 'moveTo') }),
|
|
74
|
+
lineTo: (h, args) => push(h.wrapper, { t: 'lineTo', p: points(args, 1, 1, 'lineTo') }),
|
|
75
|
+
quadTo: (h, args) => push(h.wrapper, { t: 'quadTo', p: points(args, 1, 2, 'quadTo') }),
|
|
76
|
+
cubicTo: (h, args) => push(h.wrapper, { t: 'cubicTo', p: points(args, 1, 3, 'cubicTo') }),
|
|
77
|
+
close: (h) => push(h.wrapper, { t: 'close', p: [] }),
|
|
78
|
+
reset: (h) => {
|
|
79
|
+
h.cmds.length = 0
|
|
80
|
+
const w = h.wrapper
|
|
81
|
+
w.arr.length = 0
|
|
82
|
+
},
|
|
83
|
+
add: (h, args) => {
|
|
84
|
+
const other = wrapped(args, 1, Path, 'add')
|
|
85
|
+
const m = args[2] === undefined ? null : wrapped(args, 2, Mat2D, 'add')
|
|
86
|
+
// a path added to itself adds what it held
|
|
87
|
+
const source = other.cmds.slice()
|
|
88
|
+
for (let n = 0; n < source.length; n++) push(h.wrapper, m ? transformCommand(source[n], m) : source[n])
|
|
89
|
+
},
|
|
90
|
+
contours: (h) => measureOf(commandsToContours(h.cmds), true, append),
|
|
91
|
+
measure: (h) => measureOf(commandsToContours(h.cmds), false, append),
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @param {Command} cmd
|
|
97
|
+
* @param {number[]} m
|
|
98
|
+
* @returns {Command}
|
|
99
|
+
*/
|
|
100
|
+
function transformCommand(cmd, m) {
|
|
101
|
+
const p = []
|
|
102
|
+
for (let i = 0; i < cmd.p.length; i += 2) {
|
|
103
|
+
const q = apply(m, cmd.p[i], cmd.p[i + 1])
|
|
104
|
+
p.push(q[0], q[1])
|
|
105
|
+
}
|
|
106
|
+
return { t: cmd.t, p }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* A new empty path.
|
|
111
|
+
*
|
|
112
|
+
* @param {Command[]} [cmds] commands to start with
|
|
113
|
+
* @returns {Wrapped}
|
|
114
|
+
*/
|
|
115
|
+
export function newPath(cmds) {
|
|
116
|
+
/** @type {PathData} */
|
|
117
|
+
const host = { cmds: [], wrapper: null }
|
|
118
|
+
const w = Path.wrap(host)
|
|
119
|
+
host.wrapper = w
|
|
120
|
+
if (cmds) for (let n = 0; n < cmds.length; n++) push(w, cmds[n])
|
|
121
|
+
return w
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @param {Contour[]} contours Lottie vertex arrays
|
|
126
|
+
* @returns {Wrapped}
|
|
127
|
+
*/
|
|
128
|
+
export function pathFromContours(contours) {
|
|
129
|
+
return newPath(contoursToCommands(contours))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @param {any} v a value a script returned
|
|
134
|
+
* @returns {Command[] | undefined} its commands when it is a path
|
|
135
|
+
*/
|
|
136
|
+
export function commandsOf(v) {
|
|
137
|
+
const h = Path.unwrap(v)
|
|
138
|
+
return h === undefined ? undefined : h.cmds
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Installs the `Path` global.
|
|
143
|
+
*
|
|
144
|
+
* @param {import('../../../luau/vm/vm.js').VM} vm
|
|
145
|
+
*/
|
|
146
|
+
export function installPath(vm) {
|
|
147
|
+
const table = new LuaTable()
|
|
148
|
+
table.set('new', new NativeFunction('Path.new', function () {
|
|
149
|
+
return newPath()
|
|
150
|
+
}))
|
|
151
|
+
vm.globals.set('Path', table)
|
|
152
|
+
}
|
|
153
|
+
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rive's `Renderer` as a recorder: the script's `draw(self, renderer)` calls
|
|
3
|
+
* land in a list of drawings, each a path as Lottie vertex arrays under the
|
|
4
|
+
* transform, clips and opacity in force when it was drawn, with a copy of the
|
|
5
|
+
* paint. A player turns the list into shapes (a group with the path, a fill or
|
|
6
|
+
* stroke, and the clips as masks).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Mat2D, identity, multiply } from '../math/math.mat2d.js'
|
|
10
|
+
import { defineType, wrapped, num } from '../api.type.js'
|
|
11
|
+
import { channels } from '../math/math.color.js'
|
|
12
|
+
import { Paint, snapshotPaint } from './draw.paint.js'
|
|
13
|
+
import { Path } from './draw.path.js'
|
|
14
|
+
import { commandsToContours, transformContours } from './draw.contours.js'
|
|
15
|
+
import { drawImage, drawImageMesh } from '../media/media.draw.js'
|
|
16
|
+
|
|
17
|
+
/** @import { Contour } from './draw.contours.js' */
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Also in the list, from other APIs: `{ kind: 'image' }` (media/media.draw.js), `{ kind: 'text' }` (text/text.js) and `{ kind: 'artboard' }` (artboard/artboard.js).
|
|
21
|
+
*
|
|
22
|
+
* @typedef {object} Drawing
|
|
23
|
+
* @property {'path'} kind
|
|
24
|
+
* @property {Contour[]} path vertex arrays with the transform already applied
|
|
25
|
+
* @property {number[]} transform the matrix that was in force, [xx, xy, yx, yy, tx, ty]
|
|
26
|
+
* @property {Contour[][]} clips the clip paths in force, each transformed
|
|
27
|
+
* @property {number} opacity 0 to 1, from `modulateOpacity`
|
|
28
|
+
* @property {ReturnType<typeof snapshotPaint>} paint
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {object} RenderState
|
|
33
|
+
* @property {number[]} m
|
|
34
|
+
* @property {Contour[][]} clips
|
|
35
|
+
* @property {number} opacity
|
|
36
|
+
* @property {number} color the colour modulation, ARGB
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/** What a `draw` call records; the host keeps one and `reset`s it for each frame */
|
|
40
|
+
export class Recorder {
|
|
41
|
+
/** @param {any[]} [into] a list to fill instead of a new one (it is emptied) */
|
|
42
|
+
reset(into) {
|
|
43
|
+
if (into) into.length = 0
|
|
44
|
+
this.drawings = into || []
|
|
45
|
+
this.state.m = identity()
|
|
46
|
+
this.state.clips = []
|
|
47
|
+
this.state.opacity = 1
|
|
48
|
+
this.state.color = 0xffffffff
|
|
49
|
+
this.stack.length = 0
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
constructor() {
|
|
53
|
+
/** @type {any[]} */
|
|
54
|
+
this.drawings = []
|
|
55
|
+
/** @type {RenderState} */
|
|
56
|
+
this.state = { m: identity(), clips: [], opacity: 1, color: 0xffffffff }
|
|
57
|
+
/** @type {RenderState[]} */
|
|
58
|
+
this.stack = []
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {number} a ARGB
|
|
64
|
+
* @param {number} b ARGB
|
|
65
|
+
* @returns {number} the channel-wise product
|
|
66
|
+
*/
|
|
67
|
+
function modulate(a, b) {
|
|
68
|
+
const p = channels(a)
|
|
69
|
+
const q = channels(b)
|
|
70
|
+
return Math.round((p[3] * q[3]) / 255) * 16777216 + Math.round((p[0] * q[0]) / 255) * 65536 + Math.round((p[1] * q[1]) / 255) * 256 + Math.round((p[2] * q[2]) / 255)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const Renderer = defineType('Renderer', {
|
|
74
|
+
methods: {
|
|
75
|
+
save: (r) => {
|
|
76
|
+
r.stack.push({ m: r.state.m, clips: r.state.clips, opacity: r.state.opacity, color: r.state.color })
|
|
77
|
+
},
|
|
78
|
+
restore: (r) => {
|
|
79
|
+
const s = r.stack.pop()
|
|
80
|
+
if (s) r.state = s
|
|
81
|
+
},
|
|
82
|
+
transform: (r, args) => {
|
|
83
|
+
r.state = { m: multiply(r.state.m, wrapped(args, 1, Mat2D, 'transform')), clips: r.state.clips, opacity: r.state.opacity, color: r.state.color }
|
|
84
|
+
},
|
|
85
|
+
modulateOpacity: (r, args) => {
|
|
86
|
+
r.state = { m: r.state.m, clips: r.state.clips, opacity: r.state.opacity * num(args, 1, 'modulateOpacity'), color: r.state.color }
|
|
87
|
+
},
|
|
88
|
+
modulateColor: (r, args) => {
|
|
89
|
+
r.state = { m: r.state.m, clips: r.state.clips, opacity: r.state.opacity, color: modulate(r.state.color, num(args, 1, 'modulateColor')) }
|
|
90
|
+
},
|
|
91
|
+
setColorModulation: (r, args) => {
|
|
92
|
+
r.state = { m: r.state.m, clips: r.state.clips, opacity: r.state.opacity, color: num(args, 1, 'setColorModulation') }
|
|
93
|
+
},
|
|
94
|
+
clipPath: (r, args) => {
|
|
95
|
+
const path = wrapped(args, 1, Path, 'clipPath')
|
|
96
|
+
r.state = { m: r.state.m, clips: r.state.clips.concat([ commandsToContours(path.cmds, r.state.m) ]), opacity: r.state.opacity, color: r.state.color }
|
|
97
|
+
},
|
|
98
|
+
drawPath: (r, args) => {
|
|
99
|
+
const path = wrapped(args, 1, Path, 'drawPath')
|
|
100
|
+
const paint = wrapped(args, 2, Paint, 'drawPath')
|
|
101
|
+
const s = r.state
|
|
102
|
+
r.drawings.push({
|
|
103
|
+
kind: 'path',
|
|
104
|
+
path: transformContours(commandsToContours(path.cmds), s.m),
|
|
105
|
+
transform: s.m.slice(),
|
|
106
|
+
clips: s.clips,
|
|
107
|
+
opacity: s.opacity,
|
|
108
|
+
paint: snapshotPaint(paint, s.color),
|
|
109
|
+
})
|
|
110
|
+
},
|
|
111
|
+
// images are recorded (media/media.draw.js); text and artboards draw themselves into the same list
|
|
112
|
+
drawImage: (r, args) => drawImage(r, args),
|
|
113
|
+
drawImageMesh: (r, args) => drawImageMesh(r, args),
|
|
114
|
+
// instances carry a MeshInstances table this recorder has no use for
|
|
115
|
+
drawImageMeshInstanced: () => {},
|
|
116
|
+
},
|
|
117
|
+
})
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rive's `Color`: an ARGB packed into one unsigned number (`0xAARRGGBB`),
|
|
3
|
+
* as a view-model colour property holds it. The constructors keep the low 8
|
|
4
|
+
* bits of each channel.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { LuaTable, NativeFunction } from '../../../luau/vm/vm.value.js'
|
|
8
|
+
import { num, optNum } from '../api.type.js'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {number} r
|
|
12
|
+
* @param {number} g
|
|
13
|
+
* @param {number} b
|
|
14
|
+
* @param {number} a
|
|
15
|
+
* @returns {number}
|
|
16
|
+
*/
|
|
17
|
+
export function argb(r, g, b, a) {
|
|
18
|
+
return (a & 255) * 16777216 + ((r & 255) << 16) + ((g & 255) << 8) + (b & 255)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {number} c
|
|
23
|
+
* @returns {number[]} [r, g, b, a], each 0 to 255
|
|
24
|
+
*/
|
|
25
|
+
export function channels(c) {
|
|
26
|
+
const n = c % 4294967296
|
|
27
|
+
return [ (n >>> 16) & 255, (n >>> 8) & 255, n & 255, Math.floor(n / 16777216) & 255 ]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {number} c
|
|
32
|
+
* @returns {number[]} [r, g, b, a], each 0 to 1
|
|
33
|
+
*/
|
|
34
|
+
export function colorFloats(c) {
|
|
35
|
+
const ch = channels(c)
|
|
36
|
+
return [ ch[0] / 255, ch[1] / 255, ch[2] / 255, ch[3] / 255 ]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {number} index 0 red, 1 green, 2 blue, 3 alpha
|
|
41
|
+
* @returns {(args: any[]) => any}
|
|
42
|
+
*/
|
|
43
|
+
function channelFn(index) {
|
|
44
|
+
const name = [ 'red', 'green', 'blue', 'alpha' ][index]
|
|
45
|
+
return function (args) {
|
|
46
|
+
const ch = channels(num(args, 0, name))
|
|
47
|
+
if (args[1] === undefined) return ch[index]
|
|
48
|
+
ch[index] = num(args, 1, name) & 255
|
|
49
|
+
return argb(ch[0], ch[1], ch[2], ch[3])
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** @type {Record<string, (args: any[]) => any>} */
|
|
54
|
+
const FNS = {
|
|
55
|
+
rgb: (a) => argb(num(a, 0, 'rgb'), num(a, 1, 'rgb'), num(a, 2, 'rgb'), 255),
|
|
56
|
+
rgba: (a) => argb(num(a, 0, 'rgba'), num(a, 1, 'rgba'), num(a, 2, 'rgba'), num(a, 3, 'rgba')),
|
|
57
|
+
lerp: (a) => {
|
|
58
|
+
const p = channels(num(a, 0, 'lerp'))
|
|
59
|
+
const q = channels(num(a, 1, 'lerp'))
|
|
60
|
+
const t = num(a, 2, 'lerp')
|
|
61
|
+
const m = []
|
|
62
|
+
for (let i = 0; i < 4; i++) m.push(Math.round(p[i] + (q[i] - p[i]) * t))
|
|
63
|
+
return argb(m[0], m[1], m[2], m[3])
|
|
64
|
+
},
|
|
65
|
+
red: channelFn(0),
|
|
66
|
+
green: channelFn(1),
|
|
67
|
+
blue: channelFn(2),
|
|
68
|
+
alpha: channelFn(3),
|
|
69
|
+
opacity: (a) => {
|
|
70
|
+
const ch = channels(num(a, 0, 'opacity'))
|
|
71
|
+
if (a[1] === undefined) return ch[3] / 255
|
|
72
|
+
return argb(ch[0], ch[1], ch[2], Math.round(Math.max(0, Math.min(1, optNum(a, 1, 'opacity', 1))) * 255))
|
|
73
|
+
},
|
|
74
|
+
toFloat: (a) => {
|
|
75
|
+
const t = new LuaTable()
|
|
76
|
+
const f = colorFloats(num(a, 0, 'toFloat'))
|
|
77
|
+
t.setRange(1, f, 0, 4)
|
|
78
|
+
return t
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Installs the `Color` global.
|
|
84
|
+
*
|
|
85
|
+
* @param {import('../../../luau/vm/vm.js').VM} vm
|
|
86
|
+
*/
|
|
87
|
+
export function installColor(vm) {
|
|
88
|
+
const color = new LuaTable()
|
|
89
|
+
for (const name in FNS) {
|
|
90
|
+
const fn = FNS[name]
|
|
91
|
+
color.set(name, new NativeFunction('Color.' + name, function (vm2, args) {
|
|
92
|
+
return fn(args)
|
|
93
|
+
}))
|
|
94
|
+
}
|
|
95
|
+
vm.globals.set('Color', color)
|
|
96
|
+
}
|