UNPKG

46.8 kBJavaScriptView Raw
1/*!
2 * paths 3.4.2
3 * https://greensock.com
4 *
5 * Copyright 2008-2020, GreenSock. All rights reserved.
6 * Subject to the terms at https://greensock.com/standard-license or for
7 * Club GreenSock members, the agreement issued with that membership.
8 * @author: Jack Doyle, jack@greensock.com
9*/
10
11/* eslint-disable */
12var _svgPathExp = /[achlmqstvz]|(-?\d*\.?\d*(?:e[\-+]?\d+)?)[0-9]/ig,
13 _numbersExp = /(?:(-)?\d*\.?\d*(?:e[\-+]?\d+)?)[0-9]/ig,
14 _scientific = /[\+\-]?\d*\.?\d+e[\+\-]?\d+/ig,
15 _selectorExp = /(^[#\.][a-z]|[a-y][a-z])/i,
16 _DEG2RAD = Math.PI / 180,
17 _RAD2DEG = 180 / Math.PI,
18 _sin = Math.sin,
19 _cos = Math.cos,
20 _abs = Math.abs,
21 _sqrt = Math.sqrt,
22 _atan2 = Math.atan2,
23 _largeNum = 1e8,
24 _isString = function _isString(value) {
25 return typeof value === "string";
26},
27 _isNumber = function _isNumber(value) {
28 return typeof value === "number";
29},
30 _isUndefined = function _isUndefined(value) {
31 return typeof value === "undefined";
32},
33 _temp = {},
34 _temp2 = {},
35 _roundingNum = 1e5,
36 _wrapProgress = function _wrapProgress(progress) {
37 return Math.round((progress + _largeNum) % 1 * _roundingNum) / _roundingNum || (progress < 0 ? 0 : 1);
38},
39 //if progress lands on 1, the % will make it 0 which is why we || 1, but not if it's negative because it makes more sense for motion to end at 0 in that case.
40_round = function _round(value) {
41 return Math.round(value * _roundingNum) / _roundingNum || 0;
42},
43 _splitSegment = function _splitSegment(rawPath, segIndex, i, t) {
44 var segment = rawPath[segIndex],
45 shift = t === 1 ? 6 : subdivideSegment(segment, i, t);
46
47 if (shift && shift + i + 2 < segment.length) {
48 rawPath.splice(segIndex, 0, segment.slice(0, i + shift + 2));
49 segment.splice(0, i + shift);
50 return 1;
51 }
52},
53 _reverseRawPath = function _reverseRawPath(rawPath, skipOuter) {
54 var i = rawPath.length;
55
56 if (!skipOuter) {
57 rawPath.reverse();
58 }
59
60 while (i--) {
61 if (!rawPath[i].reversed) {
62 reverseSegment(rawPath[i]);
63 }
64 }
65},
66 _copyMetaData = function _copyMetaData(source, copy) {
67 copy.totalLength = source.totalLength;
68
69 if (source.samples) {
70 //segment
71 copy.samples = source.samples.slice(0);
72 copy.lookup = source.lookup.slice(0);
73 copy.minLength = source.minLength;
74 copy.resolution = source.resolution;
75 } else {
76 //rawPath
77 copy.totalPoints = source.totalPoints;
78 }
79
80 return copy;
81},
82 //pushes a new segment into a rawPath, but if its starting values match the ending values of the last segment, it'll merge it into that same segment (to reduce the number of segments)
83_appendOrMerge = function _appendOrMerge(rawPath, segment) {
84 var index = rawPath.length,
85 prevSeg = rawPath[index - 1] || [],
86 l = prevSeg.length;
87
88 if (segment[0] === prevSeg[l - 2] && segment[1] === prevSeg[l - 1]) {
89 segment = prevSeg.concat(segment.slice(2));
90 index--;
91 }
92
93 rawPath[index] = segment;
94},
95 _bestDistance;
96/* TERMINOLOGY
97 - RawPath - an array of arrays, one for each Segment. A single RawPath could have multiple "M" commands, defining Segments (paths aren't always connected).
98 - Segment - an array containing a sequence of Cubic Bezier coordinates in alternating x, y, x, y format. Starting anchor, then control point 1, control point 2, and ending anchor, then the next control point 1, control point 2, anchor, etc. Uses less memory than an array with a bunch of {x, y} points.
99 - Bezier - a single cubic Bezier with a starting anchor, two control points, and an ending anchor.
100 - the variable "t" is typically the position along an individual Bezier path (time) and it's NOT linear, meaning it could accelerate/decelerate based on the control points whereas the "p" or "progress" value is linearly mapped to the whole path, so it shouldn't really accelerate/decelerate based on control points. So a progress of 0.2 would be almost exactly 20% along the path. "t" is ONLY in an individual Bezier piece.
101 */
102//accepts basic selector text, a path instance, a RawPath instance, or a Segment and returns a RawPath (makes it easy to homogenize things). If an element or selector text is passed in, it'll also cache the value so that if it's queried again, it'll just take the path data from there instead of parsing it all over again (as long as the path data itself hasn't changed - it'll check).
103
104
105export function getRawPath(value) {
106 value = _isString(value) && _selectorExp.test(value) ? document.querySelector(value) || value : value;
107 var e = value.getAttribute ? value : 0,
108 rawPath;
109
110 if (e && (value = value.getAttribute("d"))) {
111 //implements caching
112 if (!e._gsPath) {
113 e._gsPath = {};
114 }
115
116 rawPath = e._gsPath[value];
117 return rawPath && !rawPath._dirty ? rawPath : e._gsPath[value] = stringToRawPath(value);
118 }
119
120 return !value ? console.warn("Expecting a <path> element or an SVG path data string") : _isString(value) ? stringToRawPath(value) : _isNumber(value[0]) ? [value] : value;
121} //copies a RawPath WITHOUT the length meta data (for speed)
122
123export function copyRawPath(rawPath) {
124 var a = [],
125 i = 0;
126
127 for (; i < rawPath.length; i++) {
128 a[i] = _copyMetaData(rawPath[i], rawPath[i].slice(0));
129 }
130
131 return _copyMetaData(rawPath, a);
132}
133export function reverseSegment(segment) {
134 var i = 0,
135 y;
136 segment.reverse(); //this will invert the order y, x, y, x so we must flip it back.
137
138 for (; i < segment.length; i += 2) {
139 y = segment[i];
140 segment[i] = segment[i + 1];
141 segment[i + 1] = y;
142 }
143
144 segment.reversed = !segment.reversed;
145}
146
147var _createPath = function _createPath(e, ignore) {
148 var path = document.createElementNS("http://www.w3.org/2000/svg", "path"),
149 attr = [].slice.call(e.attributes),
150 i = attr.length,
151 name;
152 ignore = "," + ignore + ",";
153
154 while (--i > -1) {
155 name = attr[i].nodeName.toLowerCase(); //in Microsoft Edge, if you don't set the attribute with a lowercase name, it doesn't render correctly! Super weird.
156
157 if (ignore.indexOf("," + name + ",") < 0) {
158 path.setAttributeNS(null, name, attr[i].nodeValue);
159 }
160 }
161
162 return path;
163},
164 _typeAttrs = {
165 rect: "rx,ry,x,y,width,height",
166 circle: "r,cx,cy",
167 ellipse: "rx,ry,cx,cy",
168 line: "x1,x2,y1,y2"
169},
170 _attrToObj = function _attrToObj(e, attrs) {
171 var props = attrs ? attrs.split(",") : [],
172 obj = {},
173 i = props.length;
174
175 while (--i > -1) {
176 obj[props[i]] = +e.getAttribute(props[i]) || 0;
177 }
178
179 return obj;
180}; //converts an SVG shape like <circle>, <rect>, <polygon>, <polyline>, <ellipse>, etc. to a <path>, swapping it in and copying the attributes to match.
181
182
183export function convertToPath(element, swap) {
184 var type = element.tagName.toLowerCase(),
185 circ = 0.552284749831,
186 data,
187 x,
188 y,
189 r,
190 ry,
191 path,
192 rcirc,
193 rycirc,
194 points,
195 w,
196 h,
197 x2,
198 x3,
199 x4,
200 x5,
201 x6,
202 y2,
203 y3,
204 y4,
205 y5,
206 y6,
207 attr;
208
209 if (type === "path" || !element.getBBox) {
210 return element;
211 }
212
213 path = _createPath(element, "x,y,width,height,cx,cy,rx,ry,r,x1,x2,y1,y2,points");
214 attr = _attrToObj(element, _typeAttrs[type]);
215
216 if (type === "rect") {
217 r = attr.rx;
218 ry = attr.ry || r;
219 x = attr.x;
220 y = attr.y;
221 w = attr.width - r * 2;
222 h = attr.height - ry * 2;
223
224 if (r || ry) {
225 //if there are rounded corners, render cubic beziers
226 x2 = x + r * (1 - circ);
227 x3 = x + r;
228 x4 = x3 + w;
229 x5 = x4 + r * circ;
230 x6 = x4 + r;
231 y2 = y + ry * (1 - circ);
232 y3 = y + ry;
233 y4 = y3 + h;
234 y5 = y4 + ry * circ;
235 y6 = y4 + ry;
236 data = "M" + x6 + "," + y3 + " V" + y4 + " C" + [x6, y5, x5, y6, x4, y6, x4 - (x4 - x3) / 3, y6, x3 + (x4 - x3) / 3, y6, x3, y6, x2, y6, x, y5, x, y4, x, y4 - (y4 - y3) / 3, x, y3 + (y4 - y3) / 3, x, y3, x, y2, x2, y, x3, y, x3 + (x4 - x3) / 3, y, x4 - (x4 - x3) / 3, y, x4, y, x5, y, x6, y2, x6, y3].join(",") + "z";
237 } else {
238 data = "M" + (x + w) + "," + y + " v" + h + " h" + -w + " v" + -h + " h" + w + "z";
239 }
240 } else if (type === "circle" || type === "ellipse") {
241 if (type === "circle") {
242 r = ry = attr.r;
243 rycirc = r * circ;
244 } else {
245 r = attr.rx;
246 ry = attr.ry;
247 rycirc = ry * circ;
248 }
249
250 x = attr.cx;
251 y = attr.cy;
252 rcirc = r * circ;
253 data = "M" + (x + r) + "," + y + " C" + [x + r, y + rycirc, x + rcirc, y + ry, x, y + ry, x - rcirc, y + ry, x - r, y + rycirc, x - r, y, x - r, y - rycirc, x - rcirc, y - ry, x, y - ry, x + rcirc, y - ry, x + r, y - rycirc, x + r, y].join(",") + "z";
254 } else if (type === "line") {
255 data = "M" + attr.x1 + "," + attr.y1 + " L" + attr.x2 + "," + attr.y2; //previously, we just converted to "Mx,y Lx,y" but Safari has bugs that cause that not to render properly when using a stroke-dasharray that's not fully visible! Using a cubic bezier fixes that issue.
256 } else if (type === "polyline" || type === "polygon") {
257 points = (element.getAttribute("points") + "").match(_numbersExp) || [];
258 x = points.shift();
259 y = points.shift();
260 data = "M" + x + "," + y + " L" + points.join(",");
261
262 if (type === "polygon") {
263 data += "," + x + "," + y + "z";
264 }
265 }
266
267 path.setAttribute("d", rawPathToString(path._gsRawPath = stringToRawPath(data)));
268
269 if (swap && element.parentNode) {
270 element.parentNode.insertBefore(path, element);
271 element.parentNode.removeChild(element);
272 }
273
274 return path;
275} //returns the rotation (in degrees) at a particular progress on a rawPath (the slope of the tangent)
276
277export function getRotationAtProgress(rawPath, progress) {
278 var d = getProgressData(rawPath, progress >= 1 ? 1 - 1e-9 : progress ? progress : 1e-9);
279 return getRotationAtBezierT(d.segment, d.i, d.t);
280}
281
282function getRotationAtBezierT(segment, i, t) {
283 var a = segment[i],
284 b = segment[i + 2],
285 c = segment[i + 4],
286 x;
287 a += (b - a) * t;
288 b += (c - b) * t;
289 a += (b - a) * t;
290 x = b + (c + (segment[i + 6] - c) * t - b) * t - a;
291 a = segment[i + 1];
292 b = segment[i + 3];
293 c = segment[i + 5];
294 a += (b - a) * t;
295 b += (c - b) * t;
296 a += (b - a) * t;
297 return _round(_atan2(b + (c + (segment[i + 7] - c) * t - b) * t - a, x) * _RAD2DEG);
298}
299
300export function sliceRawPath(rawPath, start, end) {
301 if (_isUndefined(end)) {
302 end = 1;
303 }
304
305 start = start || 0;
306 var reverse = start > end,
307 loops = Math.max(0, ~~(_abs(end - start) - 1e-8));
308
309 if (reverse) {
310 reverse = end;
311 end = start;
312 start = reverse;
313 reverse = 1;
314 loops -= loops ? 1 : 0;
315 }
316
317 if (start < 0 || end < 0) {
318 var offset = ~~Math.min(start, end) + 1;
319 start += offset;
320 end += offset;
321 }
322
323 var path = copyRawPath(rawPath.totalLength ? rawPath : cacheRawPathMeasurements(rawPath)),
324 wrap = end > 1,
325 s = getProgressData(path, start, _temp, true),
326 e = getProgressData(path, end, _temp2),
327 eSeg = e.segment,
328 sSeg = s.segment,
329 eSegIndex = e.segIndex,
330 sSegIndex = s.segIndex,
331 ei = e.i,
332 si = s.i,
333 sameSegment = sSegIndex === eSegIndex,
334 sameBezier = ei === si && sameSegment,
335 invertedOrder = sameSegment && si > ei || sameBezier && s.t > e.t,
336 sShift,
337 eShift,
338 i,
339 copy,
340 totalSegments,
341 l,
342 j;
343
344 if (wrap || loops) {
345 if (_splitSegment(path, sSegIndex, si, s.t)) {
346 sShift = 1;
347 sSegIndex++;
348
349 if (sameBezier) {
350 if (invertedOrder) {
351 e.t /= s.t;
352 } else {
353 e.t = (e.t - s.t) / (1 - s.t);
354 eSegIndex++;
355 ei = 0;
356 }
357 } else if (sSegIndex <= eSegIndex + 1 && !invertedOrder) {
358 eSegIndex++;
359
360 if (sameSegment) {
361 ei -= si;
362 }
363 }
364 }
365
366 if (!e.t) {
367 eSegIndex--;
368
369 if (reverse) {
370 sSegIndex--;
371 }
372 } else if (_splitSegment(path, eSegIndex, ei, e.t)) {
373 if (invertedOrder && sShift) {
374 sSegIndex++;
375 }
376
377 if (reverse) {
378 eSegIndex++;
379 }
380 }
381
382 copy = [];
383 totalSegments = path.length;
384 l = 1 + totalSegments * loops;
385 j = sSegIndex;
386
387 if (reverse) {
388 eSegIndex = (eSegIndex || totalSegments) - 1;
389 l += (totalSegments - eSegIndex + sSegIndex) % totalSegments;
390
391 for (i = 0; i < l; i++) {
392 _appendOrMerge(copy, path[j]);
393
394 j = (j || totalSegments) - 1;
395 }
396 } else {
397 l += (totalSegments - sSegIndex + eSegIndex) % totalSegments;
398
399 for (i = 0; i < l; i++) {
400 _appendOrMerge(copy, path[j++ % totalSegments]);
401 }
402 }
403
404 path = copy;
405 } else {
406 eShift = e.t === 1 ? 6 : subdivideSegment(eSeg, ei, e.t);
407
408 if (start !== end) {
409 sShift = subdivideSegment(sSeg, si, sameBezier ? s.t / e.t : s.t);
410
411 if (sameSegment) {
412 eShift += sShift;
413 }
414
415 eSeg.splice(ei + eShift + 2);
416
417 if (sShift || si) {
418 sSeg.splice(0, si + sShift);
419 }
420
421 i = path.length;
422
423 while (i--) {
424 //chop off any extra segments
425 if (i < sSegIndex || i > eSegIndex) {
426 path.splice(i, 1);
427 }
428 }
429 } else {
430 eSeg.angle = getRotationAtBezierT(eSeg, ei + eShift, 0); //record the value before we chop because it'll be impossible to determine the angle after its length is 0!
431
432 ei += eShift;
433 s = eSeg[ei];
434 e = eSeg[ei + 1];
435 eSeg.length = eSeg.totalLength = 0;
436 eSeg.totalPoints = path.totalPoints = 8;
437 eSeg.push(s, e, s, e, s, e, s, e);
438 }
439 }
440
441 if (reverse) {
442 _reverseRawPath(path, wrap || loops);
443 }
444
445 path.totalLength = 0;
446 return path;
447} //measures a Segment according to its resolution (so if segment.resolution is 6, for example, it'll take 6 samples equally across each Bezier) and create/populate a "samples" array that has the length up to each of those sample points (always increasing from the start) as well as a "lookup" array that's broken up according to the smallest distance between 2 samples. This gives us a very fast way of looking up a progress position rather than looping through all the points/Beziers. You can optionally have it only measure a subset, starting at startIndex and going for a specific number of beziers (remember, there are 3 x/y pairs each, for a total of 6 elements for each Bezier). It will also populate a "totalLength" property, but that's not generally super accurate because by default it'll only take 6 samples per Bezier. But for performance reasons, it's perfectly adequate for measuring progress values along the path. If you need a more accurate totalLength, either increase the resolution or use the more advanced bezierToPoints() method which keeps adding points until they don't deviate by more than a certain precision value.
448
449function measureSegment(segment, startIndex, bezierQty) {
450 startIndex = startIndex || 0;
451
452 if (!segment.samples) {
453 segment.samples = [];
454 segment.lookup = [];
455 }
456
457 var resolution = ~~segment.resolution || 12,
458 inc = 1 / resolution,
459 endIndex = bezierQty ? startIndex + bezierQty * 6 + 1 : segment.length,
460 x1 = segment[startIndex],
461 y1 = segment[startIndex + 1],
462 samplesIndex = startIndex ? startIndex / 6 * resolution : 0,
463 samples = segment.samples,
464 lookup = segment.lookup,
465 min = (startIndex ? segment.minLength : _largeNum) || _largeNum,
466 prevLength = samples[samplesIndex + bezierQty * resolution - 1],
467 length = startIndex ? samples[samplesIndex - 1] : 0,
468 i,
469 j,
470 x4,
471 x3,
472 x2,
473 xd,
474 xd1,
475 y4,
476 y3,
477 y2,
478 yd,
479 yd1,
480 inv,
481 t,
482 lengthIndex,
483 l,
484 segLength;
485 samples.length = lookup.length = 0;
486
487 for (j = startIndex + 2; j < endIndex; j += 6) {
488 x4 = segment[j + 4] - x1;
489 x3 = segment[j + 2] - x1;
490 x2 = segment[j] - x1;
491 y4 = segment[j + 5] - y1;
492 y3 = segment[j + 3] - y1;
493 y2 = segment[j + 1] - y1;
494 xd = xd1 = yd = yd1 = 0;
495
496 if (_abs(x4) < 1e-5 && _abs(y4) < 1e-5 && _abs(x2) + _abs(y2) < 1e-5) {
497 //dump points that are sufficiently close (basically right on top of each other, making a bezier super tiny or 0 length)
498 if (segment.length > 8) {
499 segment.splice(j, 6);
500 j -= 6;
501 endIndex -= 6;
502 }
503 } else {
504 for (i = 1; i <= resolution; i++) {
505 t = inc * i;
506 inv = 1 - t;
507 xd = xd1 - (xd1 = (t * t * x4 + 3 * inv * (t * x3 + inv * x2)) * t);
508 yd = yd1 - (yd1 = (t * t * y4 + 3 * inv * (t * y3 + inv * y2)) * t);
509 l = _sqrt(yd * yd + xd * xd);
510
511 if (l < min) {
512 min = l;
513 }
514
515 length += l;
516 samples[samplesIndex++] = length;
517 }
518 }
519
520 x1 += x4;
521 y1 += y4;
522 }
523
524 if (prevLength) {
525 prevLength -= length;
526
527 for (; samplesIndex < samples.length; samplesIndex++) {
528 samples[samplesIndex] += prevLength;
529 }
530 }
531
532 if (samples.length && min) {
533 segment.totalLength = segLength = samples[samples.length - 1] || 0;
534 segment.minLength = min;
535 l = lengthIndex = 0;
536
537 for (i = 0; i < segLength; i += min) {
538 lookup[l++] = samples[lengthIndex] < i ? ++lengthIndex : lengthIndex;
539 }
540 } else {
541 segment.totalLength = samples[0] = 0;
542 }
543
544 return startIndex ? length - samples[startIndex / 2 - 1] : length;
545}
546
547export function cacheRawPathMeasurements(rawPath, resolution) {
548 var pathLength, points, i;
549
550 for (i = pathLength = points = 0; i < rawPath.length; i++) {
551 rawPath[i].resolution = ~~resolution || 12; //steps per Bezier curve (anchor, 2 control points, to anchor)
552
553 points += rawPath[i].length;
554 pathLength += measureSegment(rawPath[i]);
555 }
556
557 rawPath.totalPoints = points;
558 rawPath.totalLength = pathLength;
559 return rawPath;
560} //divide segment[i] at position t (value between 0 and 1, progress along that particular cubic bezier segment that starts at segment[i]). Returns how many elements were spliced into the segment array (either 0 or 6)
561
562export function subdivideSegment(segment, i, t) {
563 if (t <= 0 || t >= 1) {
564 return 0;
565 }
566
567 var ax = segment[i],
568 ay = segment[i + 1],
569 cp1x = segment[i + 2],
570 cp1y = segment[i + 3],
571 cp2x = segment[i + 4],
572 cp2y = segment[i + 5],
573 bx = segment[i + 6],
574 by = segment[i + 7],
575 x1a = ax + (cp1x - ax) * t,
576 x2 = cp1x + (cp2x - cp1x) * t,
577 y1a = ay + (cp1y - ay) * t,
578 y2 = cp1y + (cp2y - cp1y) * t,
579 x1 = x1a + (x2 - x1a) * t,
580 y1 = y1a + (y2 - y1a) * t,
581 x2a = cp2x + (bx - cp2x) * t,
582 y2a = cp2y + (by - cp2y) * t;
583 x2 += (x2a - x2) * t;
584 y2 += (y2a - y2) * t;
585 segment.splice(i + 2, 4, _round(x1a), //first control point
586 _round(y1a), _round(x1), //second control point
587 _round(y1), _round(x1 + (x2 - x1) * t), //new fabricated anchor on line
588 _round(y1 + (y2 - y1) * t), _round(x2), //third control point
589 _round(y2), _round(x2a), //fourth control point
590 _round(y2a));
591 segment.samples && segment.samples.splice(i / 6 * segment.resolution | 0, 0, 0, 0, 0, 0, 0, 0);
592 return 6;
593} // returns an object {path, segment, segIndex, i, t}
594
595function getProgressData(rawPath, progress, decoratee, pushToNextIfAtEnd) {
596 decoratee = decoratee || {};
597 rawPath.totalLength || cacheRawPathMeasurements(rawPath);
598
599 if (progress < 0 || progress > 1) {
600 progress = _wrapProgress(progress);
601 }
602
603 var segIndex = 0,
604 segment = rawPath[0],
605 samples,
606 resolution,
607 length,
608 min,
609 max,
610 i,
611 t;
612
613 if (rawPath.length > 1) {
614 //speed optimization: most of the time, there's only one segment so skip the recursion.
615 length = rawPath.totalLength * progress;
616 max = i = 0;
617
618 while ((max += rawPath[i++].totalLength) < length) {
619 segIndex = i;
620 }
621
622 segment = rawPath[segIndex];
623 min = max - segment.totalLength;
624 progress = (length - min) / (max - min) || 0;
625 }
626
627 samples = segment.samples;
628 resolution = segment.resolution; //how many samples per cubic bezier chunk
629
630 length = segment.totalLength * progress;
631 i = segment.lookup[~~(length / segment.minLength)] || 0;
632 min = i ? samples[i - 1] : 0;
633 max = samples[i];
634
635 if (max < length) {
636 min = max;
637 max = samples[++i];
638 }
639
640 t = 1 / resolution * ((length - min) / (max - min) + i % resolution);
641 i = ~~(i / resolution) * 6;
642
643 if (pushToNextIfAtEnd && t === 1) {
644 if (i + 6 < segment.length) {
645 i += 6;
646 t = 0;
647 } else if (segIndex + 1 < rawPath.length) {
648 i = t = 0;
649 segment = rawPath[++segIndex];
650 }
651 }
652
653 decoratee.t = t;
654 decoratee.i = i;
655 decoratee.path = rawPath;
656 decoratee.segment = segment;
657 decoratee.segIndex = segIndex;
658 return decoratee;
659}
660
661export function getPositionOnPath(rawPath, progress, includeAngle, point) {
662 var segment = rawPath[0],
663 result = point || {},
664 samples,
665 resolution,
666 length,
667 min,
668 max,
669 i,
670 t,
671 a,
672 inv;
673
674 if (progress < 0 || progress > 1) {
675 progress = _wrapProgress(progress);
676 }
677
678 if (rawPath.length > 1) {
679 //speed optimization: most of the time, there's only one segment so skip the recursion.
680 length = rawPath.totalLength * progress;
681 max = i = 0;
682
683 while ((max += rawPath[i++].totalLength) < length) {
684 segment = rawPath[i];
685 }
686
687 min = max - segment.totalLength;
688 progress = (length - min) / (max - min) || 0;
689 }
690
691 samples = segment.samples;
692 resolution = segment.resolution;
693 length = segment.totalLength * progress;
694 i = segment.lookup[~~(length / segment.minLength)] || 0;
695 min = i ? samples[i - 1] : 0;
696 max = samples[i];
697
698 if (max < length) {
699 min = max;
700 max = samples[++i];
701 }
702
703 t = 1 / resolution * ((length - min) / (max - min) + i % resolution) || 0;
704 inv = 1 - t;
705 i = ~~(i / resolution) * 6;
706 a = segment[i];
707 result.x = _round((t * t * (segment[i + 6] - a) + 3 * inv * (t * (segment[i + 4] - a) + inv * (segment[i + 2] - a))) * t + a);
708 result.y = _round((t * t * (segment[i + 7] - (a = segment[i + 1])) + 3 * inv * (t * (segment[i + 5] - a) + inv * (segment[i + 3] - a))) * t + a);
709
710 if (includeAngle) {
711 result.angle = segment.totalLength ? getRotationAtBezierT(segment, i, t >= 1 ? 1 - 1e-9 : t ? t : 1e-9) : segment.angle || 0;
712 }
713
714 return result;
715} //applies a matrix transform to RawPath (or a segment in a RawPath) and returns whatever was passed in (it transforms the values in the array(s), not a copy).
716
717export function transformRawPath(rawPath, a, b, c, d, tx, ty) {
718 var j = rawPath.length,
719 segment,
720 l,
721 i,
722 x,
723 y;
724
725 while (--j > -1) {
726 segment = rawPath[j];
727 l = segment.length;
728
729 for (i = 0; i < l; i += 2) {
730 x = segment[i];
731 y = segment[i + 1];
732 segment[i] = x * a + y * c + tx;
733 segment[i + 1] = x * b + y * d + ty;
734 }
735 }
736
737 rawPath._dirty = 1;
738 return rawPath;
739} // translates SVG arc data into a segment (cubic beziers). Angle is in degrees.
740
741function arcToSegment(lastX, lastY, rx, ry, angle, largeArcFlag, sweepFlag, x, y) {
742 if (lastX === x && lastY === y) {
743 return;
744 }
745
746 rx = _abs(rx);
747 ry = _abs(ry);
748
749 var angleRad = angle % 360 * _DEG2RAD,
750 cosAngle = _cos(angleRad),
751 sinAngle = _sin(angleRad),
752 PI = Math.PI,
753 TWOPI = PI * 2,
754 dx2 = (lastX - x) / 2,
755 dy2 = (lastY - y) / 2,
756 x1 = cosAngle * dx2 + sinAngle * dy2,
757 y1 = -sinAngle * dx2 + cosAngle * dy2,
758 x1_sq = x1 * x1,
759 y1_sq = y1 * y1,
760 radiiCheck = x1_sq / (rx * rx) + y1_sq / (ry * ry);
761
762 if (radiiCheck > 1) {
763 rx = _sqrt(radiiCheck) * rx;
764 ry = _sqrt(radiiCheck) * ry;
765 }
766
767 var rx_sq = rx * rx,
768 ry_sq = ry * ry,
769 sq = (rx_sq * ry_sq - rx_sq * y1_sq - ry_sq * x1_sq) / (rx_sq * y1_sq + ry_sq * x1_sq);
770
771 if (sq < 0) {
772 sq = 0;
773 }
774
775 var coef = (largeArcFlag === sweepFlag ? -1 : 1) * _sqrt(sq),
776 cx1 = coef * (rx * y1 / ry),
777 cy1 = coef * -(ry * x1 / rx),
778 sx2 = (lastX + x) / 2,
779 sy2 = (lastY + y) / 2,
780 cx = sx2 + (cosAngle * cx1 - sinAngle * cy1),
781 cy = sy2 + (sinAngle * cx1 + cosAngle * cy1),
782 ux = (x1 - cx1) / rx,
783 uy = (y1 - cy1) / ry,
784 vx = (-x1 - cx1) / rx,
785 vy = (-y1 - cy1) / ry,
786 temp = ux * ux + uy * uy,
787 angleStart = (uy < 0 ? -1 : 1) * Math.acos(ux / _sqrt(temp)),
788 angleExtent = (ux * vy - uy * vx < 0 ? -1 : 1) * Math.acos((ux * vx + uy * vy) / _sqrt(temp * (vx * vx + vy * vy)));
789
790 isNaN(angleExtent) && (angleExtent = PI); //rare edge case. Math.cos(-1) is NaN.
791
792 if (!sweepFlag && angleExtent > 0) {
793 angleExtent -= TWOPI;
794 } else if (sweepFlag && angleExtent < 0) {
795 angleExtent += TWOPI;
796 }
797
798 angleStart %= TWOPI;
799 angleExtent %= TWOPI;
800
801 var segments = Math.ceil(_abs(angleExtent) / (TWOPI / 4)),
802 rawPath = [],
803 angleIncrement = angleExtent / segments,
804 controlLength = 4 / 3 * _sin(angleIncrement / 2) / (1 + _cos(angleIncrement / 2)),
805 ma = cosAngle * rx,
806 mb = sinAngle * rx,
807 mc = sinAngle * -ry,
808 md = cosAngle * ry,
809 i;
810
811 for (i = 0; i < segments; i++) {
812 angle = angleStart + i * angleIncrement;
813 x1 = _cos(angle);
814 y1 = _sin(angle);
815 ux = _cos(angle += angleIncrement);
816 uy = _sin(angle);
817 rawPath.push(x1 - controlLength * y1, y1 + controlLength * x1, ux + controlLength * uy, uy - controlLength * ux, ux, uy);
818 } //now transform according to the actual size of the ellipse/arc (the beziers were noramlized, between 0 and 1 on a circle).
819
820
821 for (i = 0; i < rawPath.length; i += 2) {
822 x1 = rawPath[i];
823 y1 = rawPath[i + 1];
824 rawPath[i] = x1 * ma + y1 * mc + cx;
825 rawPath[i + 1] = x1 * mb + y1 * md + cy;
826 }
827
828 rawPath[i - 2] = x; //always set the end to exactly where it's supposed to be
829
830 rawPath[i - 1] = y;
831 return rawPath;
832} //Spits back a RawPath with absolute coordinates. Each segment starts with a "moveTo" command (x coordinate, then y) and then 2 control points (x, y, x, y), then anchor. The goal is to minimize memory and maximize speed.
833
834
835export function stringToRawPath(d) {
836 var a = (d + "").replace(_scientific, function (m) {
837 var n = +m;
838 return n < 0.0001 && n > -0.0001 ? 0 : n;
839 }).match(_svgPathExp) || [],
840 //some authoring programs spit out very small numbers in scientific notation like "1e-5", so make sure we round that down to 0 first.
841 path = [],
842 relativeX = 0,
843 relativeY = 0,
844 twoThirds = 2 / 3,
845 elements = a.length,
846 points = 0,
847 errorMessage = "ERROR: malformed path: " + d,
848 i,
849 j,
850 x,
851 y,
852 command,
853 isRelative,
854 segment,
855 startX,
856 startY,
857 difX,
858 difY,
859 beziers,
860 prevCommand,
861 flag1,
862 flag2,
863 line = function line(sx, sy, ex, ey) {
864 difX = (ex - sx) / 3;
865 difY = (ey - sy) / 3;
866 segment.push(sx + difX, sy + difY, ex - difX, ey - difY, ex, ey);
867 };
868
869 if (!d || !isNaN(a[0]) || isNaN(a[1])) {
870 console.log(errorMessage);
871 return path;
872 }
873
874 for (i = 0; i < elements; i++) {
875 prevCommand = command;
876
877 if (isNaN(a[i])) {
878 command = a[i].toUpperCase();
879 isRelative = command !== a[i]; //lower case means relative
880 } else {
881 //commands like "C" can be strung together without any new command characters between.
882 i--;
883 }
884
885 x = +a[i + 1];
886 y = +a[i + 2];
887
888 if (isRelative) {
889 x += relativeX;
890 y += relativeY;
891 }
892
893 if (!i) {
894 startX = x;
895 startY = y;
896 } // "M" (move)
897
898
899 if (command === "M") {
900 if (segment) {
901 if (segment.length < 8) {
902 //if the path data was funky and just had a M with no actual drawing anywhere, skip it.
903 path.length -= 1;
904 } else {
905 points += segment.length;
906 }
907 }
908
909 relativeX = startX = x;
910 relativeY = startY = y;
911 segment = [x, y];
912 path.push(segment);
913 i += 2;
914 command = "L"; //an "M" with more than 2 values gets interpreted as "lineTo" commands ("L").
915 // "C" (cubic bezier)
916 } else if (command === "C") {
917 if (!segment) {
918 segment = [0, 0];
919 }
920
921 if (!isRelative) {
922 relativeX = relativeY = 0;
923 } //note: "*1" is just a fast/short way to cast the value as a Number. WAAAY faster in Chrome, slightly slower in Firefox.
924
925
926 segment.push(x, y, relativeX + a[i + 3] * 1, relativeY + a[i + 4] * 1, relativeX += a[i + 5] * 1, relativeY += a[i + 6] * 1);
927 i += 6; // "S" (continuation of cubic bezier)
928 } else if (command === "S") {
929 difX = relativeX;
930 difY = relativeY;
931
932 if (prevCommand === "C" || prevCommand === "S") {
933 difX += relativeX - segment[segment.length - 4];
934 difY += relativeY - segment[segment.length - 3];
935 }
936
937 if (!isRelative) {
938 relativeX = relativeY = 0;
939 }
940
941 segment.push(difX, difY, x, y, relativeX += a[i + 3] * 1, relativeY += a[i + 4] * 1);
942 i += 4; // "Q" (quadratic bezier)
943 } else if (command === "Q") {
944 difX = relativeX + (x - relativeX) * twoThirds;
945 difY = relativeY + (y - relativeY) * twoThirds;
946
947 if (!isRelative) {
948 relativeX = relativeY = 0;
949 }
950
951 relativeX += a[i + 3] * 1;
952 relativeY += a[i + 4] * 1;
953 segment.push(difX, difY, relativeX + (x - relativeX) * twoThirds, relativeY + (y - relativeY) * twoThirds, relativeX, relativeY);
954 i += 4; // "T" (continuation of quadratic bezier)
955 } else if (command === "T") {
956 difX = relativeX - segment[segment.length - 4];
957 difY = relativeY - segment[segment.length - 3];
958 segment.push(relativeX + difX, relativeY + difY, x + (relativeX + difX * 1.5 - x) * twoThirds, y + (relativeY + difY * 1.5 - y) * twoThirds, relativeX = x, relativeY = y);
959 i += 2; // "H" (horizontal line)
960 } else if (command === "H") {
961 line(relativeX, relativeY, relativeX = x, relativeY);
962 i += 1; // "V" (vertical line)
963 } else if (command === "V") {
964 //adjust values because the first (and only one) isn't x in this case, it's y.
965 line(relativeX, relativeY, relativeX, relativeY = x + (isRelative ? relativeY - relativeX : 0));
966 i += 1; // "L" (line) or "Z" (close)
967 } else if (command === "L" || command === "Z") {
968 if (command === "Z") {
969 x = startX;
970 y = startY;
971 segment.closed = true;
972 }
973
974 if (command === "L" || _abs(relativeX - x) > 0.5 || _abs(relativeY - y) > 0.5) {
975 line(relativeX, relativeY, x, y);
976
977 if (command === "L") {
978 i += 2;
979 }
980 }
981
982 relativeX = x;
983 relativeY = y; // "A" (arc)
984 } else if (command === "A") {
985 flag1 = a[i + 4];
986 flag2 = a[i + 5];
987 difX = a[i + 6];
988 difY = a[i + 7];
989 j = 7;
990
991 if (flag1.length > 1) {
992 // for cases when the flags are merged, like "a8 8 0 018 8" (the 0 and 1 flags are WITH the x value of 8, but it could also be "a8 8 0 01-8 8" so it may include x or not)
993 if (flag1.length < 3) {
994 difY = difX;
995 difX = flag2;
996 j--;
997 } else {
998 difY = flag2;
999 difX = flag1.substr(2);
1000 j -= 2;
1001 }
1002
1003 flag2 = flag1.charAt(1);
1004 flag1 = flag1.charAt(0);
1005 }
1006
1007 beziers = arcToSegment(relativeX, relativeY, +a[i + 1], +a[i + 2], +a[i + 3], +flag1, +flag2, (isRelative ? relativeX : 0) + difX * 1, (isRelative ? relativeY : 0) + difY * 1);
1008 i += j;
1009
1010 if (beziers) {
1011 for (j = 0; j < beziers.length; j++) {
1012 segment.push(beziers[j]);
1013 }
1014 }
1015
1016 relativeX = segment[segment.length - 2];
1017 relativeY = segment[segment.length - 1];
1018 } else {
1019 console.log(errorMessage);
1020 }
1021 }
1022
1023 i = segment.length;
1024
1025 if (i < 6) {
1026 //in case there's odd SVG like a M0,0 command at the very end.
1027 path.pop();
1028 i = 0;
1029 } else if (segment[0] === segment[i - 2] && segment[1] === segment[i - 1]) {
1030 segment.closed = true;
1031 }
1032
1033 path.totalPoints = points + i;
1034 return path;
1035} //populates the points array in alternating x/y values (like [x, y, x, y...] instead of individual point objects [{x, y}, {x, y}...] to conserve memory and stay in line with how we're handling segment arrays
1036
1037export function bezierToPoints(x1, y1, x2, y2, x3, y3, x4, y4, threshold, points, index) {
1038 var x12 = (x1 + x2) / 2,
1039 y12 = (y1 + y2) / 2,
1040 x23 = (x2 + x3) / 2,
1041 y23 = (y2 + y3) / 2,
1042 x34 = (x3 + x4) / 2,
1043 y34 = (y3 + y4) / 2,
1044 x123 = (x12 + x23) / 2,
1045 y123 = (y12 + y23) / 2,
1046 x234 = (x23 + x34) / 2,
1047 y234 = (y23 + y34) / 2,
1048 x1234 = (x123 + x234) / 2,
1049 y1234 = (y123 + y234) / 2,
1050 dx = x4 - x1,
1051 dy = y4 - y1,
1052 d2 = _abs((x2 - x4) * dy - (y2 - y4) * dx),
1053 d3 = _abs((x3 - x4) * dy - (y3 - y4) * dx),
1054 length;
1055
1056 if (!points) {
1057 points = [x1, y1, x4, y4];
1058 index = 2;
1059 }
1060
1061 points.splice(index || points.length - 2, 0, x1234, y1234);
1062
1063 if ((d2 + d3) * (d2 + d3) > threshold * (dx * dx + dy * dy)) {
1064 length = points.length;
1065 bezierToPoints(x1, y1, x12, y12, x123, y123, x1234, y1234, threshold, points, index);
1066 bezierToPoints(x1234, y1234, x234, y234, x34, y34, x4, y4, threshold, points, index + 2 + (points.length - length));
1067 }
1068
1069 return points;
1070}
1071/*
1072function getAngleBetweenPoints(x0, y0, x1, y1, x2, y2) { //angle between 3 points in radians
1073 var dx1 = x1 - x0,
1074 dy1 = y1 - y0,
1075 dx2 = x2 - x1,
1076 dy2 = y2 - y1,
1077 dx3 = x2 - x0,
1078 dy3 = y2 - y0,
1079 a = dx1 * dx1 + dy1 * dy1,
1080 b = dx2 * dx2 + dy2 * dy2,
1081 c = dx3 * dx3 + dy3 * dy3;
1082 return Math.acos( (a + b - c) / _sqrt(4 * a * b) );
1083},
1084*/
1085//pointsToSegment() doesn't handle flat coordinates (where y is always 0) the way we need (the resulting control points are always right on top of the anchors), so this function basically makes the control points go directly up and down, varying in length based on the curviness (more curvy, further control points)
1086
1087export function flatPointsToSegment(points, curviness) {
1088 if (curviness === void 0) {
1089 curviness = 1;
1090 }
1091
1092 var x = points[0],
1093 y = 0,
1094 segment = [x, y],
1095 i = 2;
1096
1097 for (; i < points.length; i += 2) {
1098 segment.push(x, y, points[i], y = (points[i] - x) * curviness / 2, x = points[i], -y);
1099 }
1100
1101 return segment;
1102} //points is an array of x/y points, like [x, y, x, y, x, y]
1103
1104export function pointsToSegment(points, curviness, cornerThreshold) {
1105 //points = simplifyPoints(points, tolerance);
1106 var l = points.length - 2,
1107 x = +points[0],
1108 y = +points[1],
1109 nextX = +points[2],
1110 nextY = +points[3],
1111 segment = [x, y, x, y],
1112 dx2 = nextX - x,
1113 dy2 = nextY - y,
1114 closed = Math.abs(points[l] - x) < 0.001 && Math.abs(points[l + 1] - y) < 0.001,
1115 prevX,
1116 prevY,
1117 angle,
1118 slope,
1119 i,
1120 dx1,
1121 dx3,
1122 dy1,
1123 dy3,
1124 d1,
1125 d2,
1126 a,
1127 b,
1128 c;
1129
1130 if (isNaN(cornerThreshold)) {
1131 cornerThreshold = Math.PI / 10;
1132 }
1133
1134 if (closed) {
1135 // if the start and end points are basically on top of each other, close the segment by adding the 2nd point to the end, and the 2nd-to-last point to the beginning (we'll remove them at the end, but this allows the curvature to look perfect)
1136 points.push(nextX, nextY);
1137 nextX = x;
1138 nextY = y;
1139 x = points[l - 2];
1140 y = points[l - 1];
1141 points.unshift(x, y);
1142 l += 4;
1143 }
1144
1145 curviness = curviness || curviness === 0 ? +curviness : 1;
1146
1147 for (i = 2; i < l; i += 2) {
1148 prevX = x;
1149 prevY = y;
1150 x = nextX;
1151 y = nextY;
1152 nextX = +points[i + 2];
1153 nextY = +points[i + 3];
1154 dx1 = dx2;
1155 dy1 = dy2;
1156 dx2 = nextX - x;
1157 dy2 = nextY - y;
1158 dx3 = nextX - prevX;
1159 dy3 = nextY - prevY;
1160 a = dx1 * dx1 + dy1 * dy1;
1161 b = dx2 * dx2 + dy2 * dy2;
1162 c = dx3 * dx3 + dy3 * dy3;
1163 angle = Math.acos((a + b - c) / _sqrt(4 * a * b)); //angle between the 3 points
1164
1165 d2 = angle / Math.PI * curviness; //temporary precalculation for speed (reusing d2 variable)
1166
1167 d1 = _sqrt(a) * d2; //the tighter the angle, the shorter we make the handles in proportion.
1168
1169 d2 *= _sqrt(b);
1170
1171 if (x !== prevX || y !== prevY) {
1172 if (angle > cornerThreshold) {
1173 slope = _atan2(dy3, dx3);
1174 segment.push(_round(x - _cos(slope) * d1), //first control point
1175 _round(y - _sin(slope) * d1), _round(x), //anchor
1176 _round(y), _round(x + _cos(slope) * d2), //second control point
1177 _round(y + _sin(slope) * d2));
1178 } else {
1179 slope = _atan2(dy1, dx1);
1180 segment.push(_round(x - _cos(slope) * d1), //first control point
1181 _round(y - _sin(slope) * d1));
1182 slope = _atan2(dy2, dx2);
1183 segment.push(_round(x), //anchor
1184 _round(y), _round(x + _cos(slope) * d2), //second control point
1185 _round(y + _sin(slope) * d2));
1186 }
1187 }
1188 }
1189
1190 segment.push(_round(nextX), _round(nextY), _round(nextX), _round(nextY));
1191
1192 if (closed) {
1193 segment.splice(0, 6);
1194 segment.length = segment.length - 6;
1195 }
1196
1197 return segment;
1198} //returns the squared distance between an x/y coordinate and a segment between x1/y1 and x2/y2
1199
1200function pointToSegDist(x, y, x1, y1, x2, y2) {
1201 var dx = x2 - x1,
1202 dy = y2 - y1,
1203 t;
1204
1205 if (dx || dy) {
1206 t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy);
1207
1208 if (t > 1) {
1209 x1 = x2;
1210 y1 = y2;
1211 } else if (t > 0) {
1212 x1 += dx * t;
1213 y1 += dy * t;
1214 }
1215 }
1216
1217 return Math.pow(x - x1, 2) + Math.pow(y - y1, 2);
1218}
1219
1220function simplifyStep(points, first, last, tolerance, simplified) {
1221 var maxSqDist = tolerance,
1222 firstX = points[first],
1223 firstY = points[first + 1],
1224 lastX = points[last],
1225 lastY = points[last + 1],
1226 index,
1227 i,
1228 d;
1229
1230 for (i = first + 2; i < last; i += 2) {
1231 d = pointToSegDist(points[i], points[i + 1], firstX, firstY, lastX, lastY);
1232
1233 if (d > maxSqDist) {
1234 index = i;
1235 maxSqDist = d;
1236 }
1237 }
1238
1239 if (maxSqDist > tolerance) {
1240 if (index - first > 2) {
1241 simplifyStep(points, first, index, tolerance, simplified);
1242 }
1243
1244 simplified.push(points[index], points[index + 1]);
1245
1246 if (last - index > 2) {
1247 simplifyStep(points, index, last, tolerance, simplified);
1248 }
1249 }
1250} //points is an array of x/y values like [x, y, x, y, x, y]
1251
1252
1253export function simplifyPoints(points, tolerance) {
1254 var prevX = parseFloat(points[0]),
1255 prevY = parseFloat(points[1]),
1256 temp = [prevX, prevY],
1257 l = points.length - 2,
1258 i,
1259 x,
1260 y,
1261 dx,
1262 dy,
1263 result,
1264 last;
1265 tolerance = Math.pow(tolerance || 1, 2);
1266
1267 for (i = 2; i < l; i += 2) {
1268 x = parseFloat(points[i]);
1269 y = parseFloat(points[i + 1]);
1270 dx = prevX - x;
1271 dy = prevY - y;
1272
1273 if (dx * dx + dy * dy > tolerance) {
1274 temp.push(x, y);
1275 prevX = x;
1276 prevY = y;
1277 }
1278 }
1279
1280 temp.push(parseFloat(points[l]), parseFloat(points[l + 1]));
1281 last = temp.length - 2;
1282 result = [temp[0], temp[1]];
1283 simplifyStep(temp, 0, last, tolerance, result);
1284 result.push(temp[last], temp[last + 1]);
1285 return result;
1286}
1287
1288function getClosestProgressOnBezier(iterations, px, py, start, end, slices, x0, y0, x1, y1, x2, y2, x3, y3) {
1289 var inc = (end - start) / slices,
1290 best = 0,
1291 t = start,
1292 x,
1293 y,
1294 d,
1295 dx,
1296 dy,
1297 inv;
1298 _bestDistance = _largeNum;
1299
1300 while (t <= end) {
1301 inv = 1 - t;
1302 x = inv * inv * inv * x0 + 3 * inv * inv * t * x1 + 3 * inv * t * t * x2 + t * t * t * x3;
1303 y = inv * inv * inv * y0 + 3 * inv * inv * t * y1 + 3 * inv * t * t * y2 + t * t * t * y3;
1304 dx = x - px;
1305 dy = y - py;
1306 d = dx * dx + dy * dy;
1307
1308 if (d < _bestDistance) {
1309 _bestDistance = d;
1310 best = t;
1311 }
1312
1313 t += inc;
1314 }
1315
1316 return iterations > 1 ? getClosestProgressOnBezier(iterations - 1, px, py, Math.max(best - inc, 0), Math.min(best + inc, 1), slices, x0, y0, x1, y1, x2, y2, x3, y3) : best;
1317}
1318
1319export function getClosestData(rawPath, x, y, slices) {
1320 //returns an object with the closest j, i, and t (j is the segment index, i is the index of the point in that segment, and t is the time/progress along that bezier)
1321 var closest = {
1322 j: 0,
1323 i: 0,
1324 t: 0
1325 },
1326 bestDistance = _largeNum,
1327 i,
1328 j,
1329 t,
1330 segment;
1331
1332 for (j = 0; j < rawPath.length; j++) {
1333 segment = rawPath[j];
1334
1335 for (i = 0; i < segment.length; i += 6) {
1336 t = getClosestProgressOnBezier(1, x, y, 0, 1, slices || 20, segment[i], segment[i + 1], segment[i + 2], segment[i + 3], segment[i + 4], segment[i + 5], segment[i + 6], segment[i + 7]);
1337
1338 if (bestDistance > _bestDistance) {
1339 bestDistance = _bestDistance;
1340 closest.j = j;
1341 closest.i = i;
1342 closest.t = t;
1343 }
1344 }
1345 }
1346
1347 return closest;
1348} //subdivide a Segment closest to a specific x,y coordinate
1349
1350export function subdivideSegmentNear(x, y, segment, slices, iterations) {
1351 var l = segment.length,
1352 bestDistance = _largeNum,
1353 bestT = 0,
1354 bestSegmentIndex = 0,
1355 t,
1356 i;
1357 slices = slices || 20;
1358 iterations = iterations || 3;
1359
1360 for (i = 0; i < l; i += 6) {
1361 t = getClosestProgressOnBezier(1, x, y, 0, 1, slices, segment[i], segment[i + 1], segment[i + 2], segment[i + 3], segment[i + 4], segment[i + 5], segment[i + 6], segment[i + 7]);
1362
1363 if (bestDistance > _bestDistance) {
1364 bestDistance = _bestDistance;
1365 bestT = t;
1366 bestSegmentIndex = i;
1367 }
1368 }
1369
1370 t = getClosestProgressOnBezier(iterations, x, y, bestT - 0.05, bestT + 0.05, slices, segment[bestSegmentIndex], segment[bestSegmentIndex + 1], segment[bestSegmentIndex + 2], segment[bestSegmentIndex + 3], segment[bestSegmentIndex + 4], segment[bestSegmentIndex + 5], segment[bestSegmentIndex + 6], segment[bestSegmentIndex + 7]);
1371 subdivideSegment(segment, bestSegmentIndex, t);
1372 return bestSegmentIndex + 6;
1373}
1374/*
1375Takes any of the following and converts it to an all Cubic Bezier SVG data string:
1376- A <path> data string like "M0,0 L2,4 v20,15 H100"
1377- A RawPath, like [[x, y, x, y, x, y, x, y][[x, y, x, y, x, y, x, y]]
1378- A Segment, like [x, y, x, y, x, y, x, y]
1379
1380Note: all numbers are rounded down to the closest 0.001 to minimize memory, maximize speed, and avoid odd numbers like 1e-13
1381*/
1382
1383export function rawPathToString(rawPath) {
1384 if (_isNumber(rawPath[0])) {
1385 //in case a segment is passed in instead
1386 rawPath = [rawPath];
1387 }
1388
1389 var result = "",
1390 l = rawPath.length,
1391 sl,
1392 s,
1393 i,
1394 segment;
1395
1396 for (s = 0; s < l; s++) {
1397 segment = rawPath[s];
1398 result += "M" + _round(segment[0]) + "," + _round(segment[1]) + " C";
1399 sl = segment.length;
1400
1401 for (i = 2; i < sl; i++) {
1402 result += _round(segment[i++]) + "," + _round(segment[i++]) + " " + _round(segment[i++]) + "," + _round(segment[i++]) + " " + _round(segment[i++]) + "," + _round(segment[i]) + " ";
1403 }
1404
1405 if (segment.closed) {
1406 result += "z";
1407 }
1408 }
1409
1410 return result;
1411}
1412/*
1413// takes a segment with coordinates [x, y, x, y, ...] and converts the control points into angles and lengths [x, y, angle, length, angle, length, x, y, angle, length, ...] so that it animates more cleanly and avoids odd breaks/kinks. For example, if you animate from 1 o'clock to 6 o'clock, it'd just go directly/linearly rather than around. So the length would be very short in the middle of the tween.
1414export function cpCoordsToAngles(segment, copy) {
1415 var result = copy ? segment.slice(0) : segment,
1416 x, y, i;
1417 for (i = 0; i < segment.length; i+=6) {
1418 x = segment[i+2] - segment[i];
1419 y = segment[i+3] - segment[i+1];
1420 result[i+2] = Math.atan2(y, x);
1421 result[i+3] = Math.sqrt(x * x + y * y);
1422 x = segment[i+6] - segment[i+4];
1423 y = segment[i+7] - segment[i+5];
1424 result[i+4] = Math.atan2(y, x);
1425 result[i+5] = Math.sqrt(x * x + y * y);
1426 }
1427 return result;
1428}
1429
1430// takes a segment that was converted with cpCoordsToAngles() to have angles and lengths instead of coordinates for the control points, and converts it BACK into coordinates.
1431export function cpAnglesToCoords(segment, copy) {
1432 var result = copy ? segment.slice(0) : segment,
1433 length = segment.length,
1434 rnd = 1000,
1435 angle, l, i, j;
1436 for (i = 0; i < length; i+=6) {
1437 angle = segment[i+2];
1438 l = segment[i+3]; //length
1439 result[i+2] = (((segment[i] + Math.cos(angle) * l) * rnd) | 0) / rnd;
1440 result[i+3] = (((segment[i+1] + Math.sin(angle) * l) * rnd) | 0) / rnd;
1441 angle = segment[i+4];
1442 l = segment[i+5]; //length
1443 result[i+4] = (((segment[i+6] - Math.cos(angle) * l) * rnd) | 0) / rnd;
1444 result[i+5] = (((segment[i+7] - Math.sin(angle) * l) * rnd) | 0) / rnd;
1445 }
1446 return result;
1447}
1448
1449//adds an "isSmooth" array to each segment and populates it with a boolean value indicating whether or not it's smooth (the control points have basically the same slope). For any smooth control points, it converts the coordinates into angle (x, in radians) and length (y) and puts them into the same index value in a smoothData array.
1450export function populateSmoothData(rawPath) {
1451 let j = rawPath.length,
1452 smooth, segment, x, y, x2, y2, i, l, a, a2, isSmooth, smoothData;
1453 while (--j > -1) {
1454 segment = rawPath[j];
1455 isSmooth = segment.isSmooth = segment.isSmooth || [0, 0, 0, 0];
1456 smoothData = segment.smoothData = segment.smoothData || [0, 0, 0, 0];
1457 isSmooth.length = 4;
1458 l = segment.length - 2;
1459 for (i = 6; i < l; i += 6) {
1460 x = segment[i] - segment[i - 2];
1461 y = segment[i + 1] - segment[i - 1];
1462 x2 = segment[i + 2] - segment[i];
1463 y2 = segment[i + 3] - segment[i + 1];
1464 a = _atan2(y, x);
1465 a2 = _atan2(y2, x2);
1466 smooth = (Math.abs(a - a2) < 0.09);
1467 if (smooth) {
1468 smoothData[i - 2] = a;
1469 smoothData[i + 2] = a2;
1470 smoothData[i - 1] = _sqrt(x * x + y * y);
1471 smoothData[i + 3] = _sqrt(x2 * x2 + y2 * y2);
1472 }
1473 isSmooth.push(smooth, smooth, 0, 0, smooth, smooth);
1474 }
1475 //if the first and last points are identical, check to see if there's a smooth transition. We must handle this a bit differently due to their positions in the array.
1476 if (segment[l] === segment[0] && segment[l+1] === segment[1]) {
1477 x = segment[0] - segment[l-2];
1478 y = segment[1] - segment[l-1];
1479 x2 = segment[2] - segment[0];
1480 y2 = segment[3] - segment[1];
1481 a = _atan2(y, x);
1482 a2 = _atan2(y2, x2);
1483 if (Math.abs(a - a2) < 0.09) {
1484 smoothData[l-2] = a;
1485 smoothData[2] = a2;
1486 smoothData[l-1] = _sqrt(x * x + y * y);
1487 smoothData[3] = _sqrt(x2 * x2 + y2 * y2);
1488 isSmooth[l-2] = isSmooth[l-1] = true; //don't change indexes 2 and 3 because we'll trigger everything from the END, and this will optimize file size a bit.
1489 }
1490 }
1491 }
1492 return rawPath;
1493}
1494export function pointToScreen(svgElement, point) {
1495 if (arguments.length < 2) { //by default, take the first set of coordinates in the path as the point
1496 let rawPath = getRawPath(svgElement);
1497 point = svgElement.ownerSVGElement.createSVGPoint();
1498 point.x = rawPath[0][0];
1499 point.y = rawPath[0][1];
1500 }
1501 return point.matrixTransform(svgElement.getScreenCTM());
1502}
1503
1504*/
\No newline at end of file