@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
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Geometry: Lottie's shapes as cubic bezier paths, the affine maths that
|
|
3
|
+
* moves them between spaces, and trimming by length.
|
|
4
|
+
*
|
|
5
|
+
* A path keeps absolute control points, so a transform maps it exactly:
|
|
6
|
+
* `cOut[k]` follows point k, `cIn[k]` leads into it. Rectangles, ellipses and
|
|
7
|
+
* stars start and wind as lottie-web builds them, since a trim depends on both.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Bezier control distance for a quarter circle */
|
|
11
|
+
const KAPPA = 0.5519
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {{ x: number, y: number }} Point
|
|
15
|
+
* @typedef {{ closed: boolean, points: Point[], cIn: Point[], cOut: Point[] }} Path
|
|
16
|
+
* @typedef {number[]} Matrix `[a, b, c, d, e, f]`, as canvas setTransform takes it
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export const IDENTITY = [ 1, 0, 0, 1, 0, 0 ]
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* `left × right`: right applies first.
|
|
23
|
+
*
|
|
24
|
+
* @param {Matrix} left
|
|
25
|
+
* @param {Matrix} right
|
|
26
|
+
* @returns {Matrix}
|
|
27
|
+
*/
|
|
28
|
+
export function multiply(left, right) {
|
|
29
|
+
return [
|
|
30
|
+
left[0] * right[0] + left[2] * right[1],
|
|
31
|
+
left[1] * right[0] + left[3] * right[1],
|
|
32
|
+
left[0] * right[2] + left[2] * right[3],
|
|
33
|
+
left[1] * right[2] + left[3] * right[3],
|
|
34
|
+
left[0] * right[4] + left[2] * right[5] + left[4],
|
|
35
|
+
left[1] * right[4] + left[3] * right[5] + left[5],
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {Matrix} m
|
|
41
|
+
* @param {number} x
|
|
42
|
+
* @param {number} y
|
|
43
|
+
* @returns {Point}
|
|
44
|
+
*/
|
|
45
|
+
export function apply(m, x, y) {
|
|
46
|
+
return { x: m[0] * x + m[2] * y + m[4], y: m[1] * x + m[3] * y + m[5] }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A Lottie transform's matrix: anchor, scale, skew, rotation, then position,
|
|
51
|
+
* in lottie-web's order.
|
|
52
|
+
*
|
|
53
|
+
* @param {number[]} anchor
|
|
54
|
+
* @param {number[]} position
|
|
55
|
+
* @param {number[]} scale percent
|
|
56
|
+
* @param {number} rotation degrees, clockwise
|
|
57
|
+
* @param {number} skew degrees
|
|
58
|
+
* @param {number} skewAxis degrees
|
|
59
|
+
* @returns {Matrix}
|
|
60
|
+
*/
|
|
61
|
+
export function transformMatrix(anchor, position, scale, rotation, skew, skewAxis) {
|
|
62
|
+
let m = [ 1, 0, 0, 1, -anchor[0], -anchor[1] ]
|
|
63
|
+
m = multiply([ scale[0] / 100, 0, 0, scale[1] / 100, 0, 0 ], m)
|
|
64
|
+
if (skew) {
|
|
65
|
+
const axis = (skewAxis * Math.PI) / 180
|
|
66
|
+
const shear = Math.tan((-skew * Math.PI) / 180)
|
|
67
|
+
const cos = Math.cos(axis)
|
|
68
|
+
const sin = Math.sin(axis)
|
|
69
|
+
m = multiply([ cos, -sin, sin, cos, 0, 0 ], m)
|
|
70
|
+
m = multiply([ 1, 0, shear, 1, 0, 0 ], m)
|
|
71
|
+
m = multiply([ cos, sin, -sin, cos, 0, 0 ], m)
|
|
72
|
+
}
|
|
73
|
+
if (rotation) {
|
|
74
|
+
const angle = (rotation * Math.PI) / 180
|
|
75
|
+
m = multiply([ Math.cos(angle), Math.sin(angle), -Math.sin(angle), Math.cos(angle), 0, 0 ], m)
|
|
76
|
+
}
|
|
77
|
+
return multiply([ 1, 0, 0, 1, position[0], position[1] ], m)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @param {Path} path
|
|
82
|
+
* @param {Matrix} m
|
|
83
|
+
* @returns {Path}
|
|
84
|
+
*/
|
|
85
|
+
export function transformPath(path, m) {
|
|
86
|
+
const map = (/** @type {Point[]} */ list) => list.map((p) => apply(m, p.x, p.y))
|
|
87
|
+
return { closed: path.closed, points: map(path.points), cIn: map(path.cIn), cOut: map(path.cOut) }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Traces a path onto the context's current path.
|
|
92
|
+
*
|
|
93
|
+
* @param {CanvasRenderingContext2D} ctx
|
|
94
|
+
* @param {Path} path
|
|
95
|
+
*/
|
|
96
|
+
export function trace(ctx, path) {
|
|
97
|
+
const { points, cIn, cOut } = path
|
|
98
|
+
if (!points.length) return
|
|
99
|
+
|
|
100
|
+
ctx.moveTo(points[0].x, points[0].y)
|
|
101
|
+
for (let k = 1; k < points.length; k++) segment(ctx, points[k - 1], cOut[k - 1], cIn[k], points[k])
|
|
102
|
+
if (path.closed) {
|
|
103
|
+
const last = points.length - 1
|
|
104
|
+
segment(ctx, points[last], cOut[last], cIn[0], points[0])
|
|
105
|
+
ctx.closePath()
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* One segment: a line where both handles sit on their points, as most of a
|
|
111
|
+
* Lottie's do, else the cubic. A line is far cheaper for the canvas to fill
|
|
112
|
+
* and stroke than a curve, and draws the same.
|
|
113
|
+
*
|
|
114
|
+
* @param {CanvasRenderingContext2D} ctx
|
|
115
|
+
* @param {Point} from
|
|
116
|
+
* @param {Point} out from's out handle
|
|
117
|
+
* @param {Point} into to's in handle
|
|
118
|
+
* @param {Point} to
|
|
119
|
+
*/
|
|
120
|
+
function segment(ctx, from, out, into, to) {
|
|
121
|
+
const isStraight = out.x === from.x && out.y === from.y && into.x === to.x && into.y === to.y
|
|
122
|
+
if (isStraight) ctx.lineTo(to.x, to.y)
|
|
123
|
+
else ctx.bezierCurveTo(out.x, out.y, into.x, into.y, to.x, to.y)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Shapes
|
|
127
|
+
// ■■■■■■
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Lottie's own `{ v, i, o, c }`, tangents relative to their vertex.
|
|
131
|
+
*
|
|
132
|
+
* @param {any} shape
|
|
133
|
+
* @returns {Path}
|
|
134
|
+
*/
|
|
135
|
+
export function fromLottie(shape) {
|
|
136
|
+
const points = []
|
|
137
|
+
const cIn = []
|
|
138
|
+
const cOut = []
|
|
139
|
+
const vertices = shape.v || []
|
|
140
|
+
for (let k = 0; k < vertices.length; k++) {
|
|
141
|
+
const v = vertices[k]
|
|
142
|
+
const i = (shape.i && shape.i[k]) || [ 0, 0 ]
|
|
143
|
+
const o = (shape.o && shape.o[k]) || [ 0, 0 ]
|
|
144
|
+
points.push({ x: v[0], y: v[1] })
|
|
145
|
+
cIn.push({ x: v[0] + i[0], y: v[1] + i[1] })
|
|
146
|
+
cOut.push({ x: v[0] + o[0], y: v[1] + o[1] })
|
|
147
|
+
}
|
|
148
|
+
return { closed: !!shape.c, points, cIn, cOut }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* A rectangle centred on `position`, its corners rounded by `roundness`.
|
|
153
|
+
* Winds clockwise from the top right, or the other way when `reversed`.
|
|
154
|
+
*
|
|
155
|
+
* @param {number[]} position
|
|
156
|
+
* @param {number[]} size
|
|
157
|
+
* @param {number} roundness
|
|
158
|
+
* @param {boolean} reversed
|
|
159
|
+
* @returns {Path}
|
|
160
|
+
*/
|
|
161
|
+
export function rectangle(position, size, roundness, reversed) {
|
|
162
|
+
const w = size[0] / 2
|
|
163
|
+
const h = size[1] / 2
|
|
164
|
+
const r = Math.min(roundness || 0, w, h)
|
|
165
|
+
const [ x, y ] = position
|
|
166
|
+
const c = r * (1 - KAPPA)
|
|
167
|
+
|
|
168
|
+
/** @type {[number, number, number, number, number, number][]} point, then in and out control offsets */
|
|
169
|
+
const corners = r
|
|
170
|
+
? [
|
|
171
|
+
[ x + w, y - h + r, 0, -r + c, 0, 0 ],
|
|
172
|
+
[ x + w, y + h - r, 0, 0, 0, r - c ],
|
|
173
|
+
[ x + w - r, y + h, r - c, 0, 0, 0 ],
|
|
174
|
+
[ x - w + r, y + h, 0, 0, -(r - c), 0 ],
|
|
175
|
+
[ x - w, y + h - r, 0, r - c, 0, 0 ],
|
|
176
|
+
[ x - w, y - h + r, 0, 0, 0, -(r - c) ],
|
|
177
|
+
[ x - w + r, y - h, -(r - c), 0, 0, 0 ],
|
|
178
|
+
[ x + w - r, y - h, 0, 0, r - c, 0 ],
|
|
179
|
+
]
|
|
180
|
+
: [ [ x + w, y - h, 0, 0, 0, 0 ], [ x + w, y + h, 0, 0, 0, 0 ], [ x - w, y + h, 0, 0, 0, 0 ], [ x - w, y - h, 0, 0, 0, 0 ] ]
|
|
181
|
+
|
|
182
|
+
const ordered = reversed ? reverseCorners(corners) : corners
|
|
183
|
+
return {
|
|
184
|
+
closed: true,
|
|
185
|
+
points: ordered.map((p) => ({ x: p[0], y: p[1] })),
|
|
186
|
+
cIn: ordered.map((p) => ({ x: p[0] + p[2], y: p[1] + p[3] })),
|
|
187
|
+
cOut: ordered.map((p) => ({ x: p[0] + p[4], y: p[1] + p[5] })),
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The same corners the other way round: each point's in and out swap.
|
|
193
|
+
*
|
|
194
|
+
* @param {[number, number, number, number, number, number][]} corners
|
|
195
|
+
*/
|
|
196
|
+
function reverseCorners(corners) {
|
|
197
|
+
const reversed = []
|
|
198
|
+
for (let k = corners.length - 1; k >= 0; k--) {
|
|
199
|
+
const p = corners[k]
|
|
200
|
+
reversed.push(/** @type {[number, number, number, number, number, number]} */ ([ p[0], p[1], p[4], p[5], p[2], p[3] ]))
|
|
201
|
+
}
|
|
202
|
+
return reversed
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* An ellipse centred on `position`, from its top, clockwise unless `reversed`.
|
|
207
|
+
*
|
|
208
|
+
* @param {number[]} position
|
|
209
|
+
* @param {number[]} size
|
|
210
|
+
* @param {boolean} reversed
|
|
211
|
+
* @returns {Path}
|
|
212
|
+
*/
|
|
213
|
+
export function ellipse(position, size, reversed) {
|
|
214
|
+
const [ x, y ] = position
|
|
215
|
+
const rx = size[0] / 2
|
|
216
|
+
const ry = size[1] / 2
|
|
217
|
+
const kx = rx * KAPPA
|
|
218
|
+
const ky = ry * KAPPA
|
|
219
|
+
const direction = reversed ? -1 : 1
|
|
220
|
+
|
|
221
|
+
const points = [ { x, y: y - ry }, { x: x + direction * rx, y }, { x, y: y + ry }, { x: x - direction * rx, y } ]
|
|
222
|
+
const cIn = [ { x: x - direction * kx, y: y - ry }, { x: x + direction * rx, y: y - ky }, { x: x + direction * kx, y: y + ry }, { x: x - direction * rx, y: y + ky } ]
|
|
223
|
+
const cOut = [ { x: x + direction * kx, y: y - ry }, { x: x + direction * rx, y: y + ky }, { x: x - direction * kx, y: y + ry }, { x: x - direction * rx, y: y - ky } ]
|
|
224
|
+
return { closed: true, points, cIn, cOut }
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* A star (`sy` 1) or polygon (`sy` 2), as lottie-web builds them.
|
|
229
|
+
*
|
|
230
|
+
* @param {{ type: number, points: number, position: number[], rotation: number, outer: number, inner: number, outerRound: number, innerRound: number, reversed: boolean }} star
|
|
231
|
+
* @returns {Path}
|
|
232
|
+
*/
|
|
233
|
+
export function star(star) {
|
|
234
|
+
const isStar = star.type === 1
|
|
235
|
+
const count = Math.floor(star.points) * (isStar ? 2 : 1)
|
|
236
|
+
const angleStep = (2 * Math.PI) / count
|
|
237
|
+
const direction = star.reversed ? -1 : 1
|
|
238
|
+
let angle = (star.rotation * Math.PI) / 180 - Math.PI / 2
|
|
239
|
+
|
|
240
|
+
const points = []
|
|
241
|
+
const cIn = []
|
|
242
|
+
const cOut = []
|
|
243
|
+
for (let k = 0; k < count; k++) {
|
|
244
|
+
const isOuter = !isStar || k % 2 === 0
|
|
245
|
+
const radius = isOuter ? star.outer : star.inner
|
|
246
|
+
const round = (isOuter ? star.outerRound : star.innerRound) / 100
|
|
247
|
+
const perimeter = (2 * Math.PI * radius) / (count * 2)
|
|
248
|
+
const x = star.position[0] + radius * Math.cos(angle)
|
|
249
|
+
const y = star.position[1] + radius * Math.sin(angle)
|
|
250
|
+
const tx = -Math.sin(angle) * perimeter * round * direction
|
|
251
|
+
const ty = Math.cos(angle) * perimeter * round * direction
|
|
252
|
+
points.push({ x, y })
|
|
253
|
+
cIn.push({ x: x - tx, y: y - ty })
|
|
254
|
+
cOut.push({ x: x + tx, y: y + ty })
|
|
255
|
+
angle += angleStep * direction
|
|
256
|
+
}
|
|
257
|
+
return { closed: true, points, cIn, cOut }
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Trimming
|
|
261
|
+
// ■■■■■■■■
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* The segments of a path, each as its four bezier points.
|
|
265
|
+
*
|
|
266
|
+
* @param {Path} path
|
|
267
|
+
* @returns {Point[][]}
|
|
268
|
+
*/
|
|
269
|
+
function segments(path) {
|
|
270
|
+
const list = []
|
|
271
|
+
const { points, cIn, cOut } = path
|
|
272
|
+
for (let k = 1; k < points.length; k++) list.push([ points[k - 1], cOut[k - 1], cIn[k], points[k] ])
|
|
273
|
+
if (path.closed && points.length > 1) {
|
|
274
|
+
const last = points.length - 1
|
|
275
|
+
list.push([ points[last], cOut[last], cIn[0], points[0] ])
|
|
276
|
+
}
|
|
277
|
+
return list
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* @param {Point[]} s four bezier points
|
|
282
|
+
* @param {number} t
|
|
283
|
+
*/
|
|
284
|
+
function pointAt(s, t) {
|
|
285
|
+
const u = 1 - t
|
|
286
|
+
return {
|
|
287
|
+
x: u * u * u * s[0].x + 3 * u * u * t * s[1].x + 3 * u * t * t * s[2].x + t * t * t * s[3].x,
|
|
288
|
+
y: u * u * u * s[0].y + 3 * u * u * t * s[1].y + 3 * u * t * t * s[2].y + t * t * t * s[3].y,
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** Samples per segment when measuring length */
|
|
293
|
+
const STEPS = 24
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* @param {Point[]} s
|
|
297
|
+
* @returns {number[]} cumulative length at each of STEPS + 1 samples
|
|
298
|
+
*/
|
|
299
|
+
function lengths(s) {
|
|
300
|
+
const table = [ 0 ]
|
|
301
|
+
let previous = s[0]
|
|
302
|
+
for (let n = 1; n <= STEPS; n++) {
|
|
303
|
+
const point = pointAt(s, n / STEPS)
|
|
304
|
+
const dx = point.x - previous.x
|
|
305
|
+
const dy = point.y - previous.y
|
|
306
|
+
table.push(table[n - 1] + Math.sqrt(dx * dx + dy * dy))
|
|
307
|
+
previous = point
|
|
308
|
+
}
|
|
309
|
+
return table
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* The bezier parameter at a distance along a segment.
|
|
314
|
+
*
|
|
315
|
+
* @param {number[]} table from lengths()
|
|
316
|
+
* @param {number} distance
|
|
317
|
+
*/
|
|
318
|
+
function parameterAt(table, distance) {
|
|
319
|
+
const total = table[table.length - 1]
|
|
320
|
+
if (distance <= 0 || total === 0) return 0
|
|
321
|
+
if (distance >= total) return 1
|
|
322
|
+
|
|
323
|
+
for (let n = 1; n < table.length; n++) {
|
|
324
|
+
if (table[n] >= distance) {
|
|
325
|
+
const span = table[n] - table[n - 1]
|
|
326
|
+
const within = span > 0 ? (distance - table[n - 1]) / span : 0
|
|
327
|
+
return (n - 1 + within) / STEPS
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return 1
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* The part of a segment between two parameters, by de Casteljau.
|
|
335
|
+
*
|
|
336
|
+
* @param {Point[]} s
|
|
337
|
+
* @param {number} t0
|
|
338
|
+
* @param {number} t1
|
|
339
|
+
* @returns {Point[]}
|
|
340
|
+
*/
|
|
341
|
+
function subSegment(s, t0, t1) {
|
|
342
|
+
const split = (/** @type {Point[]} */ q, /** @type {number} */ t) => {
|
|
343
|
+
const mix = (/** @type {Point} */ a, /** @type {Point} */ b) => ({ x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t })
|
|
344
|
+
const ab = mix(q[0], q[1])
|
|
345
|
+
const bc = mix(q[1], q[2])
|
|
346
|
+
const cd = mix(q[2], q[3])
|
|
347
|
+
const abc = mix(ab, bc)
|
|
348
|
+
const bcd = mix(bc, cd)
|
|
349
|
+
const mid = mix(abc, bcd)
|
|
350
|
+
return { before: [ q[0], ab, abc, mid ], after: [ mid, bcd, cd, q[3] ] }
|
|
351
|
+
}
|
|
352
|
+
const tail = split(s, t0).after
|
|
353
|
+
const span = t0 < 1 ? (t1 - t0) / (1 - t0) : 0
|
|
354
|
+
return split(tail, Math.min(1, Math.max(0, span))).before
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* The stretch of a path between two distances along it, as an open path.
|
|
359
|
+
*
|
|
360
|
+
* @param {Point[][]} list the path's segments
|
|
361
|
+
* @param {number[][]} tables each segment's lengths()
|
|
362
|
+
* @param {number} from
|
|
363
|
+
* @param {number} to
|
|
364
|
+
* @returns {Path | null}
|
|
365
|
+
*/
|
|
366
|
+
function between(list, tables, from, to) {
|
|
367
|
+
/** @type {Path} */
|
|
368
|
+
const path = { closed: false, points: [], cIn: [], cOut: [] }
|
|
369
|
+
let offset = 0
|
|
370
|
+
for (let n = 0; n < list.length; n++) {
|
|
371
|
+
const length = tables[n][tables[n].length - 1]
|
|
372
|
+
const start = Math.max(from, offset)
|
|
373
|
+
const end = Math.min(to, offset + length)
|
|
374
|
+
if (end > start || (length === 0 && start === offset && start <= to && start >= from)) {
|
|
375
|
+
const piece = subSegment(list[n], parameterAt(tables[n], start - offset), parameterAt(tables[n], end - offset))
|
|
376
|
+
if (!path.points.length) {
|
|
377
|
+
path.points.push(piece[0])
|
|
378
|
+
path.cIn.push(piece[0])
|
|
379
|
+
}
|
|
380
|
+
path.cOut[path.points.length - 1] = piece[1]
|
|
381
|
+
path.points.push(piece[3])
|
|
382
|
+
path.cIn.push(piece[2])
|
|
383
|
+
path.cOut.push(piece[3])
|
|
384
|
+
}
|
|
385
|
+
offset += length
|
|
386
|
+
}
|
|
387
|
+
return path.points.length > 1 ? path : null
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Trim Paths: the part of each path between `start` and `end`, percent of its
|
|
392
|
+
* length, shifted by `offset` degrees (360 is the whole length). `together`
|
|
393
|
+
* (`m` 2) treats every path as one, in order; otherwise each is trimmed alone.
|
|
394
|
+
*
|
|
395
|
+
* @param {Path[]} paths
|
|
396
|
+
* @param {number} start
|
|
397
|
+
* @param {number} end
|
|
398
|
+
* @param {number} offset
|
|
399
|
+
* @param {boolean} together
|
|
400
|
+
* @returns {Path[][]} what each path keeps, in the same order
|
|
401
|
+
*/
|
|
402
|
+
export function trimEach(paths, start, end, offset, together) {
|
|
403
|
+
let s = Math.min(start, end) / 100
|
|
404
|
+
let e = Math.max(start, end) / 100
|
|
405
|
+
if (e - s >= 1) return paths.map((path) => [ path ])
|
|
406
|
+
if (e === s) return paths.map(() => [])
|
|
407
|
+
|
|
408
|
+
const shift = (offset % 360) / 360
|
|
409
|
+
s += shift
|
|
410
|
+
e += shift
|
|
411
|
+
// the range may run past the end and wrap round to the start
|
|
412
|
+
const ranges = s >= 1 || s < 0 ? wrapRanges(s, e) : e > 1 ? [ [ s, 1 ], [ 0, e - 1 ] ] : [ [ s, e ] ]
|
|
413
|
+
|
|
414
|
+
const measured = paths.map((path) => {
|
|
415
|
+
const list = segments(path)
|
|
416
|
+
const tables = list.map(lengths)
|
|
417
|
+
let total = 0
|
|
418
|
+
for (let n = 0; n < tables.length; n++) total += tables[n][tables[n].length - 1]
|
|
419
|
+
return { list, tables, total }
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
let combined = 0
|
|
423
|
+
for (let n = 0; n < measured.length; n++) combined += measured[n].total
|
|
424
|
+
|
|
425
|
+
const kept = []
|
|
426
|
+
let before = 0
|
|
427
|
+
for (let n = 0; n < measured.length; n++) {
|
|
428
|
+
const m = measured[n]
|
|
429
|
+
// alone, a path's range is its own length; together, a share of the combined one
|
|
430
|
+
const scale = together ? combined : m.total
|
|
431
|
+
const origin = together ? before : 0
|
|
432
|
+
const pieces = []
|
|
433
|
+
for (let r = 0; r < ranges.length; r++) {
|
|
434
|
+
const from = ranges[r][0] * scale - origin
|
|
435
|
+
const to = ranges[r][1] * scale - origin
|
|
436
|
+
if (to <= 0 || from >= m.total) continue
|
|
437
|
+
const piece = between(m.list, m.tables, Math.max(0, from), Math.min(m.total, to))
|
|
438
|
+
if (piece) pieces.push(piece)
|
|
439
|
+
}
|
|
440
|
+
kept.push(pieces)
|
|
441
|
+
before += m.total
|
|
442
|
+
}
|
|
443
|
+
return kept
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* The same trim, every path's pieces in one list.
|
|
448
|
+
*
|
|
449
|
+
* @param {Path[]} paths
|
|
450
|
+
* @param {number} start
|
|
451
|
+
* @param {number} end
|
|
452
|
+
* @param {number} offset
|
|
453
|
+
* @param {boolean} together
|
|
454
|
+
* @returns {Path[]}
|
|
455
|
+
*/
|
|
456
|
+
export function trim(paths, start, end, offset, together) {
|
|
457
|
+
const kept = trimEach(paths, start, end, offset, together)
|
|
458
|
+
const flat = []
|
|
459
|
+
for (let n = 0; n < kept.length; n++) for (let k = 0; k < kept[n].length; k++) flat.push(kept[n][k])
|
|
460
|
+
return flat
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* A range shifted wholly outside 0-1, folded back into it.
|
|
465
|
+
*
|
|
466
|
+
* @param {number} s
|
|
467
|
+
* @param {number} e
|
|
468
|
+
*/
|
|
469
|
+
function wrapRanges(s, e) {
|
|
470
|
+
const shift = Math.floor(s)
|
|
471
|
+
const a = s - shift
|
|
472
|
+
const b = e - shift
|
|
473
|
+
return b > 1 ? [ [ a, 1 ], [ 0, b - 1 ] ] : [ [ a, b ] ]
|
|
474
|
+
}
|