@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
package/skin/skin.ik.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inverse kinematics for a skin's bone chain, from rive-runtime's
|
|
3
|
+
* `IKConstraint` (src/constraints/ik_constraint.cpp): one bone points at the
|
|
4
|
+
* target, two are solved by the law of cosines, a longer chain solves each
|
|
5
|
+
* bone against the tip in turn, and strength blends the solved angles with
|
|
6
|
+
* the angles the chain had. The converter solves the same way
|
|
7
|
+
* (rive/transformer/constraint/constraint.ik.js).
|
|
8
|
+
*
|
|
9
|
+
* It runs whenever a bound value moves a skin, so it allocates nothing: the
|
|
10
|
+
* caller fills `LOCALS` and `PARENT`, `solve` works in preallocated buffers,
|
|
11
|
+
* and the solved worlds are in `WORLDS`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** The most bones in a chain; a longer one is left as the bones' own values pose it */
|
|
15
|
+
export const MAX_CHAIN = 32
|
|
16
|
+
|
|
17
|
+
/** The chain's local matrices before the solve, six numbers a bone */
|
|
18
|
+
export const LOCALS = new Float64Array(MAX_CHAIN * 6)
|
|
19
|
+
/** The world matrix of the top bone's parent */
|
|
20
|
+
export const PARENT = new Float64Array(6)
|
|
21
|
+
/** The solved world matrices, six numbers a bone; the first six are the parent's, so a bone's parent is the slot before it */
|
|
22
|
+
export const WORLDS = new Float64Array((MAX_CHAIN + 1) * 6)
|
|
23
|
+
|
|
24
|
+
// what the solve starts from: each bone's own x, y, scale, skew and rotation, and its parent's world, inverted
|
|
25
|
+
const COMPONENTS = new Float64Array(MAX_CHAIN * 6)
|
|
26
|
+
const INVERSES = new Float64Array(MAX_CHAIN * 6)
|
|
27
|
+
const ANGLES = new Float64Array(MAX_CHAIN)
|
|
28
|
+
|
|
29
|
+
const TAU = Math.PI * 2
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* `world[slot] = world[parent] × local`, the matrices six numbers each.
|
|
33
|
+
*
|
|
34
|
+
* @param {number} bone the bone's place in the chain
|
|
35
|
+
*/
|
|
36
|
+
function compose(bone) {
|
|
37
|
+
const p = bone * 6
|
|
38
|
+
const q = bone * 6
|
|
39
|
+
const o = (bone + 1) * 6
|
|
40
|
+
const a0 = WORLDS[p], a1 = WORLDS[p + 1], a2 = WORLDS[p + 2], a3 = WORLDS[p + 3], a4 = WORLDS[p + 4], a5 = WORLDS[p + 5]
|
|
41
|
+
const b0 = LOCALS[q], b1 = LOCALS[q + 1], b2 = LOCALS[q + 2], b3 = LOCALS[q + 3], b4 = LOCALS[q + 4], b5 = LOCALS[q + 5]
|
|
42
|
+
WORLDS[o] = a0 * b0 + a2 * b1
|
|
43
|
+
WORLDS[o + 1] = a1 * b0 + a3 * b1
|
|
44
|
+
WORLDS[o + 2] = a0 * b2 + a2 * b3
|
|
45
|
+
WORLDS[o + 3] = a1 * b2 + a3 * b3
|
|
46
|
+
WORLDS[o + 4] = a0 * b4 + a2 * b5 + a4
|
|
47
|
+
WORLDS[o + 5] = a1 * b4 + a3 * b5 + a5
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The bone's parent's world, inverted (the identity where it has no inverse).
|
|
52
|
+
*
|
|
53
|
+
* @param {number} bone
|
|
54
|
+
*/
|
|
55
|
+
function invertParent(bone) {
|
|
56
|
+
const p = bone * 6
|
|
57
|
+
const o = bone * 6
|
|
58
|
+
const a = WORLDS[p], b = WORLDS[p + 1], c = WORLDS[p + 2], d = WORLDS[p + 3], tx = WORLDS[p + 4], ty = WORLDS[p + 5]
|
|
59
|
+
let det = a * d - b * c
|
|
60
|
+
if (det === 0) {
|
|
61
|
+
INVERSES[o] = 1
|
|
62
|
+
INVERSES[o + 1] = 0
|
|
63
|
+
INVERSES[o + 2] = 0
|
|
64
|
+
INVERSES[o + 3] = 1
|
|
65
|
+
INVERSES[o + 4] = 0
|
|
66
|
+
INVERSES[o + 5] = 0
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
det = 1 / det
|
|
70
|
+
INVERSES[o] = d * det
|
|
71
|
+
INVERSES[o + 1] = -b * det
|
|
72
|
+
INVERSES[o + 2] = -c * det
|
|
73
|
+
INVERSES[o + 3] = a * det
|
|
74
|
+
INVERSES[o + 4] = (c * ty - d * tx) * det
|
|
75
|
+
INVERSES[o + 5] = (b * tx - a * ty) * det
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The bone's local matrix at a rotation, its position, scale and skew as it began.
|
|
80
|
+
*
|
|
81
|
+
* @param {number} bone
|
|
82
|
+
* @param {number} rotation
|
|
83
|
+
*/
|
|
84
|
+
function turn(bone, rotation) {
|
|
85
|
+
const c = bone * 6
|
|
86
|
+
const cos = rotation === 0 ? 1 : Math.cos(rotation)
|
|
87
|
+
const sin = rotation === 0 ? 0 : Math.sin(rotation)
|
|
88
|
+
const a = cos * COMPONENTS[c + 2]
|
|
89
|
+
const b = sin * COMPONENTS[c + 2]
|
|
90
|
+
let m2 = -sin * COMPONENTS[c + 3]
|
|
91
|
+
let m3 = cos * COMPONENTS[c + 3]
|
|
92
|
+
const skew = COMPONENTS[c + 4]
|
|
93
|
+
if (skew !== 0) {
|
|
94
|
+
m2 = a * skew + m2
|
|
95
|
+
m3 = b * skew + m3
|
|
96
|
+
}
|
|
97
|
+
LOCALS[c] = a
|
|
98
|
+
LOCALS[c + 1] = b
|
|
99
|
+
LOCALS[c + 2] = m2
|
|
100
|
+
LOCALS[c + 3] = m3
|
|
101
|
+
LOCALS[c + 4] = COMPONENTS[c]
|
|
102
|
+
LOCALS[c + 5] = COMPONENTS[c + 1]
|
|
103
|
+
compose(bone)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* `std::max(-1, std::min(1, x))`, which sends NaN to 1 as the C++ library does.
|
|
108
|
+
*
|
|
109
|
+
* @param {number} x
|
|
110
|
+
*/
|
|
111
|
+
function clamp(x) {
|
|
112
|
+
const low = x < 1 ? x : 1
|
|
113
|
+
return -1 < low ? low : -1
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The shorter turn from one angle to another, each first taken modulo a full turn.
|
|
118
|
+
*
|
|
119
|
+
* @param {number} from
|
|
120
|
+
* @param {number} to
|
|
121
|
+
*/
|
|
122
|
+
function difference(from, to) {
|
|
123
|
+
let diff = (to % TAU) - (from % TAU)
|
|
124
|
+
if (diff > Math.PI) diff -= TAU
|
|
125
|
+
else if (diff < -Math.PI) diff += TAU
|
|
126
|
+
return diff
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @param {number} bone
|
|
131
|
+
* @returns {number} the x of the bone's world translation
|
|
132
|
+
*/
|
|
133
|
+
const worldX = (bone) => WORLDS[(bone + 1) * 6 + 4]
|
|
134
|
+
/** @param {number} bone */
|
|
135
|
+
const worldY = (bone) => WORLDS[(bone + 1) * 6 + 5]
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* A point through the matrix of a bone's parent's inverse, as x or y.
|
|
139
|
+
*
|
|
140
|
+
* @param {number} bone
|
|
141
|
+
* @param {number} x
|
|
142
|
+
* @param {number} y
|
|
143
|
+
* @param {boolean} wantY
|
|
144
|
+
*/
|
|
145
|
+
function parentSpace(bone, x, y, wantY) {
|
|
146
|
+
const i = bone * 6
|
|
147
|
+
return wantY ? INVERSES[i + 1] * x + INVERSES[i + 3] * y + INVERSES[i + 5] : INVERSES[i] * x + INVERSES[i + 2] * y + INVERSES[i + 4]
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Solves the chain in `LOCALS` against a target, leaving the worlds in `WORLDS`.
|
|
152
|
+
*
|
|
153
|
+
* @param {number} count bones in the chain, the top one first
|
|
154
|
+
* @param {number} tipLength the last bone's length
|
|
155
|
+
* @param {number} tx the target's world x
|
|
156
|
+
* @param {number} ty the target's world y
|
|
157
|
+
* @param {boolean} invert
|
|
158
|
+
* @param {number} strength 0..1
|
|
159
|
+
*/
|
|
160
|
+
export function solve(count, tipLength, tx, ty, invert, strength) {
|
|
161
|
+
for (let k = 0; k < 6; k++) WORLDS[k] = PARENT[k]
|
|
162
|
+
for (let n = 0; n < count; n++) {
|
|
163
|
+
const c = n * 6
|
|
164
|
+
// Mat2D::decompose of the bone's own matrix
|
|
165
|
+
const m0 = LOCALS[c], m1 = LOCALS[c + 1], m2 = LOCALS[c + 2], m3 = LOCALS[c + 3]
|
|
166
|
+
const denom = m0 * m0 + m1 * m1
|
|
167
|
+
const scaleX = Math.sqrt(denom)
|
|
168
|
+
COMPONENTS[c] = LOCALS[c + 4]
|
|
169
|
+
COMPONENTS[c + 1] = LOCALS[c + 5]
|
|
170
|
+
COMPONENTS[c + 2] = scaleX
|
|
171
|
+
COMPONENTS[c + 3] = scaleX === 0 ? 0 : (m0 * m3 - m2 * m1) / scaleX
|
|
172
|
+
COMPONENTS[c + 4] = Math.atan2(m0 * m2 + m1 * m3, denom)
|
|
173
|
+
COMPONENTS[c + 5] = Math.atan2(m1, m0)
|
|
174
|
+
ANGLES[n] = COMPONENTS[c + 5]
|
|
175
|
+
compose(n)
|
|
176
|
+
}
|
|
177
|
+
for (let n = 0; n < count; n++) invertParent(n)
|
|
178
|
+
|
|
179
|
+
if (count === 1) {
|
|
180
|
+
solveOne(tx, ty)
|
|
181
|
+
} else if (count === 2) {
|
|
182
|
+
solveTwo(0, 1, tipLength, tx, ty, invert)
|
|
183
|
+
} else {
|
|
184
|
+
const last = count - 1
|
|
185
|
+
for (let n = 0; n < last; n++) {
|
|
186
|
+
solveTwo(n, last, tipLength, tx, ty, invert)
|
|
187
|
+
for (let j = n + 1; j < last; j++) invertParent(j)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (strength !== 1) {
|
|
192
|
+
for (let n = 0; n < count; n++) {
|
|
193
|
+
const from = COMPONENTS[n * 6 + 5] % TAU
|
|
194
|
+
turn(n, from + difference(from, ANGLES[n]) * strength)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* @param {number} tx
|
|
201
|
+
* @param {number} ty
|
|
202
|
+
*/
|
|
203
|
+
function solveOne(tx, ty) {
|
|
204
|
+
const x = INVERSES[0] * (tx - worldX(0)) + INVERSES[2] * (ty - worldY(0))
|
|
205
|
+
const y = INVERSES[1] * (tx - worldX(0)) + INVERSES[3] * (ty - worldY(0))
|
|
206
|
+
const r = Math.atan2(y, x)
|
|
207
|
+
turn(0, r)
|
|
208
|
+
ANGLES[0] = r
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* @param {number} first the bone solved against the target
|
|
213
|
+
* @param {number} last the tip, which the solve brings to the target
|
|
214
|
+
* @param {number} tipLength
|
|
215
|
+
* @param {number} tx
|
|
216
|
+
* @param {number} ty
|
|
217
|
+
* @param {boolean} invert
|
|
218
|
+
*/
|
|
219
|
+
function solveTwo(first, last, tipLength, tx, ty, invert) {
|
|
220
|
+
const child = first + 1
|
|
221
|
+
const aX = parentSpace(first, worldX(first), worldY(first), false)
|
|
222
|
+
const aY = parentSpace(first, worldX(first), worldY(first), true)
|
|
223
|
+
const cX = parentSpace(first, worldX(child), worldY(child), false)
|
|
224
|
+
const cY = parentSpace(first, worldX(child), worldY(child), true)
|
|
225
|
+
const lw = (last + 1) * 6
|
|
226
|
+
const tipX = WORLDS[lw] * tipLength + WORLDS[lw + 4]
|
|
227
|
+
const tipY = WORLDS[lw + 1] * tipLength + WORLDS[lw + 5]
|
|
228
|
+
const bX = parentSpace(first, tipX, tipY, false)
|
|
229
|
+
const bY = parentSpace(first, tipX, tipY, true)
|
|
230
|
+
const tX = parentSpace(first, tx, ty, false)
|
|
231
|
+
const tY = parentSpace(first, tx, ty, true)
|
|
232
|
+
|
|
233
|
+
const a = Math.sqrt((bX - cX) * (bX - cX) + (bY - cY) * (bY - cY))
|
|
234
|
+
const b = Math.sqrt((cX - aX) * (cX - aX) + (cY - aY) * (cY - aY))
|
|
235
|
+
const cvX = tX - aX
|
|
236
|
+
const cvY = tY - aY
|
|
237
|
+
const c = Math.sqrt(cvX * cvX + cvY * cvY)
|
|
238
|
+
|
|
239
|
+
const A = Math.acos(clamp((-a * a + b * b + c * c) / (2 * b * c)))
|
|
240
|
+
const C = Math.acos(clamp((a * a + b * b - c * c) / (2 * a * b)))
|
|
241
|
+
|
|
242
|
+
let correction = 0
|
|
243
|
+
if (last !== child) {
|
|
244
|
+
// the tip is further down: the bone after the child turns the correction
|
|
245
|
+
const dx = tipX - worldX(child)
|
|
246
|
+
const dy = tipY - worldY(child)
|
|
247
|
+
const lx = INVERSES[(first + 2) * 6] * dx + INVERSES[(first + 2) * 6 + 2] * dy
|
|
248
|
+
const ly = INVERSES[(first + 2) * 6 + 1] * dx + INVERSES[(first + 2) * 6 + 3] * dy
|
|
249
|
+
correction = -Math.atan2(ly, lx)
|
|
250
|
+
}
|
|
251
|
+
const toTarget = Math.atan2(cvY, cvX)
|
|
252
|
+
const r1 = invert ? toTarget - A : A + toTarget
|
|
253
|
+
const r2 = (invert ? -C + Math.PI : C - Math.PI) + correction
|
|
254
|
+
|
|
255
|
+
turn(first, r1)
|
|
256
|
+
turn(child, r2)
|
|
257
|
+
if (child !== last) compose(last)
|
|
258
|
+
ANGLES[first] = r1
|
|
259
|
+
ANGLES[child] = r2
|
|
260
|
+
}
|
package/skin/skin.js
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skins: the paths a bundle's `skins` deform from the values data binds set.
|
|
3
|
+
* A bind's source is a slot's source (a view-model path and a converter),
|
|
4
|
+
* read by the caller's `read`; each time one of a skin's bound values changes
|
|
5
|
+
* the skin's path is recomputed, to be written into the slot `sid` as any slot
|
|
6
|
+
* value.
|
|
7
|
+
*
|
|
8
|
+
* A skin may also hold IK constraints over its bones (`ik`), whose strength
|
|
9
|
+
* and target position data can bind as well: they are solved, one chain each,
|
|
10
|
+
* before the bones deform the path.
|
|
11
|
+
*
|
|
12
|
+
* Under the bound values play what an animation keys in the rig (`keys`),
|
|
13
|
+
* as Rive applies animations first and binds after: a bound channel overrides
|
|
14
|
+
* the keyed one, and the rest keep following the animation. A skin an
|
|
15
|
+
* animation keys is written as a function of the frame, for the player to ask
|
|
16
|
+
* as it draws; any other as the path itself. In a nested machine that plays
|
|
17
|
+
* several animations at once, each channel is mixed toward each pose that
|
|
18
|
+
* keys it by the pose's weight, as Rive applies them, before the bones deform
|
|
19
|
+
* the path; the player passes the poses of the machine the path is drawn in.
|
|
20
|
+
*
|
|
21
|
+
* Nothing is allocated once a skin is made: its values, the scratch it
|
|
22
|
+
* deforms in and the paths it writes are made up front and reused.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { BONE_CHANNELS, createPath, createRig, deformRig, IK_CHANNELS, NODE_CHANNELS, VERTEX_CHANNELS } from './skin.deform.js'
|
|
26
|
+
import { settledWeight } from '../mixer/mixer.mix.js'
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {import('./skin.deform.js').Skin} Skin
|
|
30
|
+
* @typedef {import('./skin.deform.js').LottiePath} LottiePath
|
|
31
|
+
* @typedef {import('./skin.deform.js').Rig} Rig
|
|
32
|
+
*
|
|
33
|
+
* @typedef {object} State
|
|
34
|
+
* @property {Skin} skin
|
|
35
|
+
* @property {Rig} rig
|
|
36
|
+
* @property {number[]} at where in `rig.now` each bind's value goes
|
|
37
|
+
* @property {any[]} sources each bind's source
|
|
38
|
+
* @property {Float64Array} values each bind's value
|
|
39
|
+
* @property {Uint8Array} has whether each bind has one
|
|
40
|
+
* @property {number} version counts the changes of the bound values
|
|
41
|
+
* @property {number} done the version the static path was made for
|
|
42
|
+
* @property {LottiePath} path the path while no animation keys the skin
|
|
43
|
+
* @property {Record<string, Played | null>} played what an animation keys, by animation id
|
|
44
|
+
*
|
|
45
|
+
* @typedef {object} Played
|
|
46
|
+
* @property {number[]} at where in `rig.now` each track's value goes
|
|
47
|
+
* @property {number[][]} keys each track's `[frame, value, ...]`
|
|
48
|
+
* @property {Array<number[] | null>} segments each track's keying segments on a nested machine's stitched timeline, null outside one
|
|
49
|
+
* @property {LottiePath} path
|
|
50
|
+
* @property {number} frame the frame the path was made for
|
|
51
|
+
* @property {number} done the version of the bound values it was made for
|
|
52
|
+
* @property {any} poses the poses it was made for
|
|
53
|
+
* @property {(frame: number, poses?: Pose[] | null) => LottiePath} fn
|
|
54
|
+
*
|
|
55
|
+
* @typedef {{ start: number, frame: number, weight: number }} Pose a nested machine's animation playing: its segment's first frame, how far in it is, and its weight
|
|
56
|
+
*
|
|
57
|
+
* @typedef {object} Skins
|
|
58
|
+
* @property {number} count skins held
|
|
59
|
+
* @property {(read: (source: any) => any) => void} update reads every bound value; a skin whose values have not changed keeps its path
|
|
60
|
+
* @property {(animation?: string) => Record<string, any>} table by slot id, the value of each skin with a bound value, for the animation with that id
|
|
61
|
+
* @property {(read: (source: any) => any, animation?: string) => Record<string, any>} evaluate `update`, then `table`
|
|
62
|
+
* @property {(sid: string, animation: string) => any} keyedBy the skin's value in an animation that keys it, else undefined: a pose that does not key a skin leaves it alone
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @param {Skin[] | undefined} list a bundle's `skins`
|
|
67
|
+
* @returns {Skins}
|
|
68
|
+
*/
|
|
69
|
+
export function createSkins(list) {
|
|
70
|
+
/** @type {State[]} */
|
|
71
|
+
const states = []
|
|
72
|
+
for (let n = 0; n < (list || []).length; n++) states.push(makeState(/** @type {Skin[]} */ (list)[n]))
|
|
73
|
+
/** @type {Record<string, Record<string, any>>} */
|
|
74
|
+
const tables = {}
|
|
75
|
+
|
|
76
|
+
/** @param {string} [animation] */
|
|
77
|
+
function table(animation) {
|
|
78
|
+
const key = animation || ''
|
|
79
|
+
const found = tables[key] || (tables[key] = {})
|
|
80
|
+
for (let n = 0; n < states.length; n++) {
|
|
81
|
+
const state = states[n]
|
|
82
|
+
if (isBound(state)) found[state.skin.sid] = valueOf(state, key)
|
|
83
|
+
else delete found[state.skin.sid]
|
|
84
|
+
}
|
|
85
|
+
return found
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** @param {(source: any) => any} read */
|
|
89
|
+
function update(read) {
|
|
90
|
+
for (let n = 0; n < states.length; n++) read1(states[n], read)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** @param {string} sid @param {string} animation */
|
|
94
|
+
function keyedBy(sid, animation) {
|
|
95
|
+
for (let n = 0; n < states.length; n++) {
|
|
96
|
+
const state = states[n]
|
|
97
|
+
const keys = state.skin.keys
|
|
98
|
+
if (state.skin.sid !== sid || !keys || !keys[animation] || !isBound(state)) continue
|
|
99
|
+
return valueOf(state, animation)
|
|
100
|
+
}
|
|
101
|
+
return undefined
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
count: states.length,
|
|
106
|
+
update,
|
|
107
|
+
table,
|
|
108
|
+
keyedBy,
|
|
109
|
+
evaluate(read, animation) {
|
|
110
|
+
update(read)
|
|
111
|
+
return table(animation)
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @param {Skin} skin
|
|
118
|
+
* @returns {State}
|
|
119
|
+
*/
|
|
120
|
+
function makeState(skin) {
|
|
121
|
+
const rig = createRig(skin)
|
|
122
|
+
/** @type {number[]} */
|
|
123
|
+
const at = []
|
|
124
|
+
/** @type {any[]} */
|
|
125
|
+
const sources = []
|
|
126
|
+
/** @param {any[]} list @param {Record<string, number>} channels @param {number} first @param {number} width */
|
|
127
|
+
const collect = (list, channels, first, width) => {
|
|
128
|
+
for (let n = 0; n < list.length; n++) {
|
|
129
|
+
const binds = list[n].binds || {}
|
|
130
|
+
for (const name in binds) {
|
|
131
|
+
if (channels[name] === undefined || !binds[name] || !binds[name].source) continue
|
|
132
|
+
at.push(first + n * width + channels[name])
|
|
133
|
+
sources.push(binds[name].source)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
collect(skin.bones, BONE_CHANNELS, 0, 6)
|
|
138
|
+
collect(skin.nodes || [], NODE_CHANNELS, rig.nodeAt, 5)
|
|
139
|
+
collect(skin.ik || [], IK_CHANNELS, rig.ikAt, 3)
|
|
140
|
+
return { skin, rig, at, sources, values: new Float64Array(at.length), has: new Uint8Array(at.length), version: 0, done: -1, path: createPath(skin), played: {} }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Reads what each bind holds now: a number, else nothing.
|
|
145
|
+
*
|
|
146
|
+
* @param {State} state
|
|
147
|
+
* @param {(source: any) => any} read
|
|
148
|
+
*/
|
|
149
|
+
function read1(state, read) {
|
|
150
|
+
let changed = false
|
|
151
|
+
for (let n = 0; n < state.sources.length; n++) {
|
|
152
|
+
const value = read(state.sources[n])
|
|
153
|
+
const number = typeof value === 'number' ? value : parseFloat(value)
|
|
154
|
+
const has = isFinite(number) ? 1 : 0
|
|
155
|
+
if (has !== state.has[n] || (has && number !== state.values[n])) changed = true
|
|
156
|
+
state.has[n] = has
|
|
157
|
+
state.values[n] = has ? number : 0
|
|
158
|
+
}
|
|
159
|
+
if (changed) state.version++
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Whether any of a skin's binds has a value: until one does the slot keeps the setup deform the bundle carries.
|
|
164
|
+
*
|
|
165
|
+
* @param {State} state
|
|
166
|
+
* @returns {boolean}
|
|
167
|
+
*/
|
|
168
|
+
function isBound(state) {
|
|
169
|
+
for (let n = 0; n < state.has.length; n++) if (state.has[n]) return true
|
|
170
|
+
return false
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The value of a bound skin's slot in an animation.
|
|
175
|
+
*
|
|
176
|
+
* @param {State} state
|
|
177
|
+
* @param {string} animation
|
|
178
|
+
* @returns {LottiePath | ((frame: number) => LottiePath)}
|
|
179
|
+
*/
|
|
180
|
+
function valueOf(state, animation) {
|
|
181
|
+
let played = state.played[animation]
|
|
182
|
+
if (played === undefined) {
|
|
183
|
+
const all = state.skin.keys
|
|
184
|
+
const keys = (all && (all[animation] || all['*'])) || []
|
|
185
|
+
if (keys.length) {
|
|
186
|
+
const at = []
|
|
187
|
+
const k = []
|
|
188
|
+
const segments = []
|
|
189
|
+
for (let n = 0; n < keys.length; n++) {
|
|
190
|
+
at.push(channelAt(state.rig, keys[n]))
|
|
191
|
+
k.push(keys[n].k)
|
|
192
|
+
segments.push(keys[n].s || null)
|
|
193
|
+
}
|
|
194
|
+
/** @type {Played} */
|
|
195
|
+
const own = { at, keys: k, segments, path: createPath(state.skin), frame: NaN, done: -1, poses: null, fn: /** @type {any} */ (null) }
|
|
196
|
+
own.fn = (frame, given) => {
|
|
197
|
+
// a machine writes its poses as a new list each time they change
|
|
198
|
+
const poses = given || null
|
|
199
|
+
if (own.frame !== frame || own.done !== state.version || own.poses !== poses) {
|
|
200
|
+
own.frame = frame
|
|
201
|
+
own.done = state.version
|
|
202
|
+
own.poses = poses
|
|
203
|
+
setValues(state, own, frame, poses)
|
|
204
|
+
deformRig(state.rig, own.path)
|
|
205
|
+
}
|
|
206
|
+
return own.path
|
|
207
|
+
}
|
|
208
|
+
played = own
|
|
209
|
+
} else {
|
|
210
|
+
played = null
|
|
211
|
+
}
|
|
212
|
+
state.played[animation] = played
|
|
213
|
+
}
|
|
214
|
+
if (played) return played.fn
|
|
215
|
+
|
|
216
|
+
if (state.done !== state.version) {
|
|
217
|
+
state.done = state.version
|
|
218
|
+
setValues(state, null, 0, null)
|
|
219
|
+
deformRig(state.rig, state.path)
|
|
220
|
+
}
|
|
221
|
+
return state.path
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* The value a track moves, as an index into `rig.now`.
|
|
226
|
+
*
|
|
227
|
+
* @param {Rig} rig
|
|
228
|
+
* @param {{ o: string, i: number, c: string }} track
|
|
229
|
+
* @returns {number}
|
|
230
|
+
*/
|
|
231
|
+
function channelAt(rig, track) {
|
|
232
|
+
if (track.o === 'n') return rig.nodeAt + track.i * 5 + NODE_CHANNELS[/** @type {keyof typeof NODE_CHANNELS} */ (track.c)]
|
|
233
|
+
if (track.o === 'v') return rig.vertexAt + track.i * 7 + VERTEX_CHANNELS[/** @type {keyof typeof VERTEX_CHANNELS} */ (track.c)]
|
|
234
|
+
return track.i * 6 + BONE_CHANNELS[/** @type {keyof typeof BONE_CHANNELS} */ (track.c)]
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Puts the values a deform uses in `rig.now`: the setup, what the animation
|
|
239
|
+
* keys at the frame, mixed toward each pose that keys a track, then what
|
|
240
|
+
* binds hold.
|
|
241
|
+
*
|
|
242
|
+
* @param {State} state
|
|
243
|
+
* @param {Played | null} played
|
|
244
|
+
* @param {number} frame
|
|
245
|
+
* @param {Pose[] | null} poses
|
|
246
|
+
*/
|
|
247
|
+
function setValues(state, played, frame, poses) {
|
|
248
|
+
const now = state.rig.now
|
|
249
|
+
now.set(state.rig.base)
|
|
250
|
+
if (played) {
|
|
251
|
+
for (let n = 0; n < played.at.length; n++) now[played.at[n]] = posedValue(played.keys[n], played.segments[n], frame, poses)
|
|
252
|
+
}
|
|
253
|
+
for (let n = 0; n < state.at.length; n++) if (state.has[n]) now[state.at[n]] = state.values[n]
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* A track's value in the poses whose segments key it, each counting as much
|
|
258
|
+
* as it does in the value Rive settles on (`settledWeight`); with none keying
|
|
259
|
+
* it, its value at the frame.
|
|
260
|
+
*
|
|
261
|
+
* @param {number[]} k
|
|
262
|
+
* @param {number[] | null} segments
|
|
263
|
+
* @param {number} frame
|
|
264
|
+
* @param {Pose[] | null} poses
|
|
265
|
+
* @returns {number}
|
|
266
|
+
*/
|
|
267
|
+
function posedValue(k, segments, frame, poses) {
|
|
268
|
+
let value = valueAt(k, frame)
|
|
269
|
+
if (!poses || !segments) return value
|
|
270
|
+
|
|
271
|
+
let total = 0
|
|
272
|
+
for (let n = 0; n < poses.length; n++) {
|
|
273
|
+
const pose = poses[n]
|
|
274
|
+
const weight = segments.indexOf(pose.start) < 0 ? 0 : settledWeight(poses, n, segments)
|
|
275
|
+
if (weight <= 0) continue
|
|
276
|
+
total += weight
|
|
277
|
+
value += (valueAt(k, pose.start + pose.frame) - value) * (weight / total)
|
|
278
|
+
}
|
|
279
|
+
return value
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* A track's value at a frame: `[frame, value, ...]` blended linearly, held
|
|
284
|
+
* before the first and after the last.
|
|
285
|
+
*
|
|
286
|
+
* @param {number[]} k
|
|
287
|
+
* @param {number} frame
|
|
288
|
+
* @returns {number}
|
|
289
|
+
*/
|
|
290
|
+
function valueAt(k, frame) {
|
|
291
|
+
if (frame <= k[0]) return k[1]
|
|
292
|
+
for (let n = 2; n < k.length; n += 2) {
|
|
293
|
+
if (frame > k[n]) continue
|
|
294
|
+
return k[n - 1] + (k[n + 1] - k[n - 1]) * ((frame - k[n - 2]) / (k[n] - k[n - 2]))
|
|
295
|
+
}
|
|
296
|
+
return k[k.length - 1]
|
|
297
|
+
}
|