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