UNPKG

46.7 kBJavaScriptView Raw
1/*!
2 * paths 3.5.1
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 reverse && sSegIndex--;
369 } else if (_splitSegment(path, eSegIndex, ei, e.t)) {
370 invertedOrder && sShift && sSegIndex++;
371 reverse && eSegIndex++;
372 }
373
374 copy = [];
375 totalSegments = path.length;
376 l = 1 + totalSegments * loops;
377 j = sSegIndex;
378
379 if (reverse) {
380 eSegIndex = (eSegIndex || totalSegments) - 1;
381 l += (totalSegments - eSegIndex + sSegIndex) % totalSegments;
382
383 for (i = 0; i < l; i++) {
384 _appendOrMerge(copy, path[j]);
385
386 j = (j || totalSegments) - 1;
387 }
388 } else {
389 l += (totalSegments - sSegIndex + eSegIndex) % totalSegments;
390
391 for (i = 0; i < l; i++) {
392 _appendOrMerge(copy, path[j++ % totalSegments]);
393 }
394 }
395
396 path = copy;
397 } else {
398 eShift = e.t === 1 ? 6 : subdivideSegment(eSeg, ei, e.t);
399
400 if (start !== end) {
401 sShift = subdivideSegment(sSeg, si, sameBezier ? s.t / e.t : s.t);
402
403 if (sameSegment) {
404 eShift += sShift;
405 }
406
407 eSeg.splice(ei + eShift + 2);
408
409 if (sShift || si) {
410 sSeg.splice(0, si + sShift);
411 }
412
413 i = path.length;
414
415 while (i--) {
416 //chop off any extra segments
417 if (i < sSegIndex || i > eSegIndex) {
418 path.splice(i, 1);
419 }
420 }
421 } else {
422 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!
423
424 ei += eShift;
425 s = eSeg[ei];
426 e = eSeg[ei + 1];
427 eSeg.length = eSeg.totalLength = 0;
428 eSeg.totalPoints = path.totalPoints = 8;
429 eSeg.push(s, e, s, e, s, e, s, e);
430 }
431 }
432
433 reverse && _reverseRawPath(path, wrap || loops);
434 path.totalLength = 0;
435 return path;
436} //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.
437
438function measureSegment(segment, startIndex, bezierQty) {
439 startIndex = startIndex || 0;
440
441 if (!segment.samples) {
442 segment.samples = [];
443 segment.lookup = [];
444 }
445
446 var resolution = ~~segment.resolution || 12,
447 inc = 1 / resolution,
448 endIndex = bezierQty ? startIndex + bezierQty * 6 + 1 : segment.length,
449 x1 = segment[startIndex],
450 y1 = segment[startIndex + 1],
451 samplesIndex = startIndex ? startIndex / 6 * resolution : 0,
452 samples = segment.samples,
453 lookup = segment.lookup,
454 min = (startIndex ? segment.minLength : _largeNum) || _largeNum,
455 prevLength = samples[samplesIndex + bezierQty * resolution - 1],
456 length = startIndex ? samples[samplesIndex - 1] : 0,
457 i,
458 j,
459 x4,
460 x3,
461 x2,
462 xd,
463 xd1,
464 y4,
465 y3,
466 y2,
467 yd,
468 yd1,
469 inv,
470 t,
471 lengthIndex,
472 l,
473 segLength;
474 samples.length = lookup.length = 0;
475
476 for (j = startIndex + 2; j < endIndex; j += 6) {
477 x4 = segment[j + 4] - x1;
478 x3 = segment[j + 2] - x1;
479 x2 = segment[j] - x1;
480 y4 = segment[j + 5] - y1;
481 y3 = segment[j + 3] - y1;
482 y2 = segment[j + 1] - y1;
483 xd = xd1 = yd = yd1 = 0;
484
485 if (_abs(x4) < 1e-5 && _abs(y4) < 1e-5 && _abs(x2) + _abs(y2) < 1e-5) {
486 //dump points that are sufficiently close (basically right on top of each other, making a bezier super tiny or 0 length)
487 if (segment.length > 8) {
488 segment.splice(j, 6);
489 j -= 6;
490 endIndex -= 6;
491 }
492 } else {
493 for (i = 1; i <= resolution; i++) {
494 t = inc * i;
495 inv = 1 - t;
496 xd = xd1 - (xd1 = (t * t * x4 + 3 * inv * (t * x3 + inv * x2)) * t);
497 yd = yd1 - (yd1 = (t * t * y4 + 3 * inv * (t * y3 + inv * y2)) * t);
498 l = _sqrt(yd * yd + xd * xd);
499
500 if (l < min) {
501 min = l;
502 }
503
504 length += l;
505 samples[samplesIndex++] = length;
506 }
507 }
508
509 x1 += x4;
510 y1 += y4;
511 }
512
513 if (prevLength) {
514 prevLength -= length;
515
516 for (; samplesIndex < samples.length; samplesIndex++) {
517 samples[samplesIndex] += prevLength;
518 }
519 }
520
521 if (samples.length && min) {
522 segment.totalLength = segLength = samples[samples.length - 1] || 0;
523 segment.minLength = min;
524 l = lengthIndex = 0;
525
526 for (i = 0; i < segLength; i += min) {
527 lookup[l++] = samples[lengthIndex] < i ? ++lengthIndex : lengthIndex;
528 }
529 } else {
530 segment.totalLength = samples[0] = 0;
531 }
532
533 return startIndex ? length - samples[startIndex / 2 - 1] : length;
534}
535
536export function cacheRawPathMeasurements(rawPath, resolution) {
537 var pathLength, points, i;
538
539 for (i = pathLength = points = 0; i < rawPath.length; i++) {
540 rawPath[i].resolution = ~~resolution || 12; //steps per Bezier curve (anchor, 2 control points, to anchor)
541
542 points += rawPath[i].length;
543 pathLength += measureSegment(rawPath[i]);
544 }
545
546 rawPath.totalPoints = points;
547 rawPath.totalLength = pathLength;
548 return rawPath;
549} //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)
550
551export function subdivideSegment(segment, i, t) {
552 if (t <= 0 || t >= 1) {
553 return 0;
554 }
555
556 var ax = segment[i],
557 ay = segment[i + 1],
558 cp1x = segment[i + 2],
559 cp1y = segment[i + 3],
560 cp2x = segment[i + 4],
561 cp2y = segment[i + 5],
562 bx = segment[i + 6],
563 by = segment[i + 7],
564 x1a = ax + (cp1x - ax) * t,
565 x2 = cp1x + (cp2x - cp1x) * t,
566 y1a = ay + (cp1y - ay) * t,
567 y2 = cp1y + (cp2y - cp1y) * t,
568 x1 = x1a + (x2 - x1a) * t,
569 y1 = y1a + (y2 - y1a) * t,
570 x2a = cp2x + (bx - cp2x) * t,
571 y2a = cp2y + (by - cp2y) * t;
572 x2 += (x2a - x2) * t;
573 y2 += (y2a - y2) * t;
574 segment.splice(i + 2, 4, _round(x1a), //first control point
575 _round(y1a), _round(x1), //second control point
576 _round(y1), _round(x1 + (x2 - x1) * t), //new fabricated anchor on line
577 _round(y1 + (y2 - y1) * t), _round(x2), //third control point
578 _round(y2), _round(x2a), //fourth control point
579 _round(y2a));
580 segment.samples && segment.samples.splice(i / 6 * segment.resolution | 0, 0, 0, 0, 0, 0, 0, 0);
581 return 6;
582} // returns an object {path, segment, segIndex, i, t}
583
584function getProgressData(rawPath, progress, decoratee, pushToNextIfAtEnd) {
585 decoratee = decoratee || {};
586 rawPath.totalLength || cacheRawPathMeasurements(rawPath);
587
588 if (progress < 0 || progress > 1) {
589 progress = _wrapProgress(progress);
590 }
591
592 var segIndex = 0,
593 segment = rawPath[0],
594 samples,
595 resolution,
596 length,
597 min,
598 max,
599 i,
600 t;
601
602 if (rawPath.length > 1) {
603 //speed optimization: most of the time, there's only one segment so skip the recursion.
604 length = rawPath.totalLength * progress;
605 max = i = 0;
606
607 while ((max += rawPath[i++].totalLength) < length) {
608 segIndex = i;
609 }
610
611 segment = rawPath[segIndex];
612 min = max - segment.totalLength;
613 progress = (length - min) / (max - min) || 0;
614 }
615
616 samples = segment.samples;
617 resolution = segment.resolution; //how many samples per cubic bezier chunk
618
619 length = segment.totalLength * progress;
620 i = segment.lookup[~~(length / segment.minLength)] || 0;
621 min = i ? samples[i - 1] : 0;
622 max = samples[i];
623
624 if (max < length) {
625 min = max;
626 max = samples[++i];
627 }
628
629 t = 1 / resolution * ((length - min) / (max - min) + i % resolution);
630 i = ~~(i / resolution) * 6;
631
632 if (pushToNextIfAtEnd && t === 1) {
633 if (i + 6 < segment.length) {
634 i += 6;
635 t = 0;
636 } else if (segIndex + 1 < rawPath.length) {
637 i = t = 0;
638 segment = rawPath[++segIndex];
639 }
640 }
641
642 decoratee.t = t;
643 decoratee.i = i;
644 decoratee.path = rawPath;
645 decoratee.segment = segment;
646 decoratee.segIndex = segIndex;
647 return decoratee;
648}
649
650export function getPositionOnPath(rawPath, progress, includeAngle, point) {
651 var segment = rawPath[0],
652 result = point || {},
653 samples,
654 resolution,
655 length,
656 min,
657 max,
658 i,
659 t,
660 a,
661 inv;
662
663 if (progress < 0 || progress > 1) {
664 progress = _wrapProgress(progress);
665 }
666
667 if (rawPath.length > 1) {
668 //speed optimization: most of the time, there's only one segment so skip the recursion.
669 length = rawPath.totalLength * progress;
670 max = i = 0;
671
672 while ((max += rawPath[i++].totalLength) < length) {
673 segment = rawPath[i];
674 }
675
676 min = max - segment.totalLength;
677 progress = (length - min) / (max - min) || 0;
678 }
679
680 samples = segment.samples;
681 resolution = segment.resolution;
682 length = segment.totalLength * progress;
683 i = segment.lookup[~~(length / segment.minLength)] || 0;
684 min = i ? samples[i - 1] : 0;
685 max = samples[i];
686
687 if (max < length) {
688 min = max;
689 max = samples[++i];
690 }
691
692 t = 1 / resolution * ((length - min) / (max - min) + i % resolution) || 0;
693 inv = 1 - t;
694 i = ~~(i / resolution) * 6;
695 a = segment[i];
696 result.x = _round((t * t * (segment[i + 6] - a) + 3 * inv * (t * (segment[i + 4] - a) + inv * (segment[i + 2] - a))) * t + a);
697 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);
698
699 if (includeAngle) {
700 result.angle = segment.totalLength ? getRotationAtBezierT(segment, i, t >= 1 ? 1 - 1e-9 : t ? t : 1e-9) : segment.angle || 0;
701 }
702
703 return result;
704} //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).
705
706export function transformRawPath(rawPath, a, b, c, d, tx, ty) {
707 var j = rawPath.length,
708 segment,
709 l,
710 i,
711 x,
712 y;
713
714 while (--j > -1) {
715 segment = rawPath[j];
716 l = segment.length;
717
718 for (i = 0; i < l; i += 2) {
719 x = segment[i];
720 y = segment[i + 1];
721 segment[i] = x * a + y * c + tx;
722 segment[i + 1] = x * b + y * d + ty;
723 }
724 }
725
726 rawPath._dirty = 1;
727 return rawPath;
728} // translates SVG arc data into a segment (cubic beziers). Angle is in degrees.
729
730function arcToSegment(lastX, lastY, rx, ry, angle, largeArcFlag, sweepFlag, x, y) {
731 if (lastX === x && lastY === y) {
732 return;
733 }
734
735 rx = _abs(rx);
736 ry = _abs(ry);
737
738 var angleRad = angle % 360 * _DEG2RAD,
739 cosAngle = _cos(angleRad),
740 sinAngle = _sin(angleRad),
741 PI = Math.PI,
742 TWOPI = PI * 2,
743 dx2 = (lastX - x) / 2,
744 dy2 = (lastY - y) / 2,
745 x1 = cosAngle * dx2 + sinAngle * dy2,
746 y1 = -sinAngle * dx2 + cosAngle * dy2,
747 x1_sq = x1 * x1,
748 y1_sq = y1 * y1,
749 radiiCheck = x1_sq / (rx * rx) + y1_sq / (ry * ry);
750
751 if (radiiCheck > 1) {
752 rx = _sqrt(radiiCheck) * rx;
753 ry = _sqrt(radiiCheck) * ry;
754 }
755
756 var rx_sq = rx * rx,
757 ry_sq = ry * ry,
758 sq = (rx_sq * ry_sq - rx_sq * y1_sq - ry_sq * x1_sq) / (rx_sq * y1_sq + ry_sq * x1_sq);
759
760 if (sq < 0) {
761 sq = 0;
762 }
763
764 var coef = (largeArcFlag === sweepFlag ? -1 : 1) * _sqrt(sq),
765 cx1 = coef * (rx * y1 / ry),
766 cy1 = coef * -(ry * x1 / rx),
767 sx2 = (lastX + x) / 2,
768 sy2 = (lastY + y) / 2,
769 cx = sx2 + (cosAngle * cx1 - sinAngle * cy1),
770 cy = sy2 + (sinAngle * cx1 + cosAngle * cy1),
771 ux = (x1 - cx1) / rx,
772 uy = (y1 - cy1) / ry,
773 vx = (-x1 - cx1) / rx,
774 vy = (-y1 - cy1) / ry,
775 temp = ux * ux + uy * uy,
776 angleStart = (uy < 0 ? -1 : 1) * Math.acos(ux / _sqrt(temp)),
777 angleExtent = (ux * vy - uy * vx < 0 ? -1 : 1) * Math.acos((ux * vx + uy * vy) / _sqrt(temp * (vx * vx + vy * vy)));
778
779 isNaN(angleExtent) && (angleExtent = PI); //rare edge case. Math.cos(-1) is NaN.
780
781 if (!sweepFlag && angleExtent > 0) {
782 angleExtent -= TWOPI;
783 } else if (sweepFlag && angleExtent < 0) {
784 angleExtent += TWOPI;
785 }
786
787 angleStart %= TWOPI;
788 angleExtent %= TWOPI;
789
790 var segments = Math.ceil(_abs(angleExtent) / (TWOPI / 4)),
791 rawPath = [],
792 angleIncrement = angleExtent / segments,
793 controlLength = 4 / 3 * _sin(angleIncrement / 2) / (1 + _cos(angleIncrement / 2)),
794 ma = cosAngle * rx,
795 mb = sinAngle * rx,
796 mc = sinAngle * -ry,
797 md = cosAngle * ry,
798 i;
799
800 for (i = 0; i < segments; i++) {
801 angle = angleStart + i * angleIncrement;
802 x1 = _cos(angle);
803 y1 = _sin(angle);
804 ux = _cos(angle += angleIncrement);
805 uy = _sin(angle);
806 rawPath.push(x1 - controlLength * y1, y1 + controlLength * x1, ux + controlLength * uy, uy - controlLength * ux, ux, uy);
807 } //now transform according to the actual size of the ellipse/arc (the beziers were noramlized, between 0 and 1 on a circle).
808
809
810 for (i = 0; i < rawPath.length; i += 2) {
811 x1 = rawPath[i];
812 y1 = rawPath[i + 1];
813 rawPath[i] = x1 * ma + y1 * mc + cx;
814 rawPath[i + 1] = x1 * mb + y1 * md + cy;
815 }
816
817 rawPath[i - 2] = x; //always set the end to exactly where it's supposed to be
818
819 rawPath[i - 1] = y;
820 return rawPath;
821} //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.
822
823
824export function stringToRawPath(d) {
825 var a = (d + "").replace(_scientific, function (m) {
826 var n = +m;
827 return n < 0.0001 && n > -0.0001 ? 0 : n;
828 }).match(_svgPathExp) || [],
829 //some authoring programs spit out very small numbers in scientific notation like "1e-5", so make sure we round that down to 0 first.
830 path = [],
831 relativeX = 0,
832 relativeY = 0,
833 twoThirds = 2 / 3,
834 elements = a.length,
835 points = 0,
836 errorMessage = "ERROR: malformed path: " + d,
837 i,
838 j,
839 x,
840 y,
841 command,
842 isRelative,
843 segment,
844 startX,
845 startY,
846 difX,
847 difY,
848 beziers,
849 prevCommand,
850 flag1,
851 flag2,
852 line = function line(sx, sy, ex, ey) {
853 difX = (ex - sx) / 3;
854 difY = (ey - sy) / 3;
855 segment.push(sx + difX, sy + difY, ex - difX, ey - difY, ex, ey);
856 };
857
858 if (!d || !isNaN(a[0]) || isNaN(a[1])) {
859 console.log(errorMessage);
860 return path;
861 }
862
863 for (i = 0; i < elements; i++) {
864 prevCommand = command;
865
866 if (isNaN(a[i])) {
867 command = a[i].toUpperCase();
868 isRelative = command !== a[i]; //lower case means relative
869 } else {
870 //commands like "C" can be strung together without any new command characters between.
871 i--;
872 }
873
874 x = +a[i + 1];
875 y = +a[i + 2];
876
877 if (isRelative) {
878 x += relativeX;
879 y += relativeY;
880 }
881
882 if (!i) {
883 startX = x;
884 startY = y;
885 } // "M" (move)
886
887
888 if (command === "M") {
889 if (segment) {
890 if (segment.length < 8) {
891 //if the path data was funky and just had a M with no actual drawing anywhere, skip it.
892 path.length -= 1;
893 } else {
894 points += segment.length;
895 }
896 }
897
898 relativeX = startX = x;
899 relativeY = startY = y;
900 segment = [x, y];
901 path.push(segment);
902 i += 2;
903 command = "L"; //an "M" with more than 2 values gets interpreted as "lineTo" commands ("L").
904 // "C" (cubic bezier)
905 } else if (command === "C") {
906 if (!segment) {
907 segment = [0, 0];
908 }
909
910 if (!isRelative) {
911 relativeX = relativeY = 0;
912 } //note: "*1" is just a fast/short way to cast the value as a Number. WAAAY faster in Chrome, slightly slower in Firefox.
913
914
915 segment.push(x, y, relativeX + a[i + 3] * 1, relativeY + a[i + 4] * 1, relativeX += a[i + 5] * 1, relativeY += a[i + 6] * 1);
916 i += 6; // "S" (continuation of cubic bezier)
917 } else if (command === "S") {
918 difX = relativeX;
919 difY = relativeY;
920
921 if (prevCommand === "C" || prevCommand === "S") {
922 difX += relativeX - segment[segment.length - 4];
923 difY += relativeY - segment[segment.length - 3];
924 }
925
926 if (!isRelative) {
927 relativeX = relativeY = 0;
928 }
929
930 segment.push(difX, difY, x, y, relativeX += a[i + 3] * 1, relativeY += a[i + 4] * 1);
931 i += 4; // "Q" (quadratic bezier)
932 } else if (command === "Q") {
933 difX = relativeX + (x - relativeX) * twoThirds;
934 difY = relativeY + (y - relativeY) * twoThirds;
935
936 if (!isRelative) {
937 relativeX = relativeY = 0;
938 }
939
940 relativeX += a[i + 3] * 1;
941 relativeY += a[i + 4] * 1;
942 segment.push(difX, difY, relativeX + (x - relativeX) * twoThirds, relativeY + (y - relativeY) * twoThirds, relativeX, relativeY);
943 i += 4; // "T" (continuation of quadratic bezier)
944 } else if (command === "T") {
945 difX = relativeX - segment[segment.length - 4];
946 difY = relativeY - segment[segment.length - 3];
947 segment.push(relativeX + difX, relativeY + difY, x + (relativeX + difX * 1.5 - x) * twoThirds, y + (relativeY + difY * 1.5 - y) * twoThirds, relativeX = x, relativeY = y);
948 i += 2; // "H" (horizontal line)
949 } else if (command === "H") {
950 line(relativeX, relativeY, relativeX = x, relativeY);
951 i += 1; // "V" (vertical line)
952 } else if (command === "V") {
953 //adjust values because the first (and only one) isn't x in this case, it's y.
954 line(relativeX, relativeY, relativeX, relativeY = x + (isRelative ? relativeY - relativeX : 0));
955 i += 1; // "L" (line) or "Z" (close)
956 } else if (command === "L" || command === "Z") {
957 if (command === "Z") {
958 x = startX;
959 y = startY;
960 segment.closed = true;
961 }
962
963 if (command === "L" || _abs(relativeX - x) > 0.5 || _abs(relativeY - y) > 0.5) {
964 line(relativeX, relativeY, x, y);
965
966 if (command === "L") {
967 i += 2;
968 }
969 }
970
971 relativeX = x;
972 relativeY = y; // "A" (arc)
973 } else if (command === "A") {
974 flag1 = a[i + 4];
975 flag2 = a[i + 5];
976 difX = a[i + 6];
977 difY = a[i + 7];
978 j = 7;
979
980 if (flag1.length > 1) {
981 // 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)
982 if (flag1.length < 3) {
983 difY = difX;
984 difX = flag2;
985 j--;
986 } else {
987 difY = flag2;
988 difX = flag1.substr(2);
989 j -= 2;
990 }
991
992 flag2 = flag1.charAt(1);
993 flag1 = flag1.charAt(0);
994 }
995
996 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);
997 i += j;
998
999 if (beziers) {
1000 for (j = 0; j < beziers.length; j++) {
1001 segment.push(beziers[j]);
1002 }
1003 }
1004
1005 relativeX = segment[segment.length - 2];
1006 relativeY = segment[segment.length - 1];
1007 } else {
1008 console.log(errorMessage);
1009 }
1010 }
1011
1012 i = segment.length;
1013
1014 if (i < 6) {
1015 //in case there's odd SVG like a M0,0 command at the very end.
1016 path.pop();
1017 i = 0;
1018 } else if (segment[0] === segment[i - 2] && segment[1] === segment[i - 1]) {
1019 segment.closed = true;
1020 }
1021
1022 path.totalPoints = points + i;
1023 return path;
1024} //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
1025
1026export function bezierToPoints(x1, y1, x2, y2, x3, y3, x4, y4, threshold, points, index) {
1027 var x12 = (x1 + x2) / 2,
1028 y12 = (y1 + y2) / 2,
1029 x23 = (x2 + x3) / 2,
1030 y23 = (y2 + y3) / 2,
1031 x34 = (x3 + x4) / 2,
1032 y34 = (y3 + y4) / 2,
1033 x123 = (x12 + x23) / 2,
1034 y123 = (y12 + y23) / 2,
1035 x234 = (x23 + x34) / 2,
1036 y234 = (y23 + y34) / 2,
1037 x1234 = (x123 + x234) / 2,
1038 y1234 = (y123 + y234) / 2,
1039 dx = x4 - x1,
1040 dy = y4 - y1,
1041 d2 = _abs((x2 - x4) * dy - (y2 - y4) * dx),
1042 d3 = _abs((x3 - x4) * dy - (y3 - y4) * dx),
1043 length;
1044
1045 if (!points) {
1046 points = [x1, y1, x4, y4];
1047 index = 2;
1048 }
1049
1050 points.splice(index || points.length - 2, 0, x1234, y1234);
1051
1052 if ((d2 + d3) * (d2 + d3) > threshold * (dx * dx + dy * dy)) {
1053 length = points.length;
1054 bezierToPoints(x1, y1, x12, y12, x123, y123, x1234, y1234, threshold, points, index);
1055 bezierToPoints(x1234, y1234, x234, y234, x34, y34, x4, y4, threshold, points, index + 2 + (points.length - length));
1056 }
1057
1058 return points;
1059}
1060/*
1061function getAngleBetweenPoints(x0, y0, x1, y1, x2, y2) { //angle between 3 points in radians
1062 var dx1 = x1 - x0,
1063 dy1 = y1 - y0,
1064 dx2 = x2 - x1,
1065 dy2 = y2 - y1,
1066 dx3 = x2 - x0,
1067 dy3 = y2 - y0,
1068 a = dx1 * dx1 + dy1 * dy1,
1069 b = dx2 * dx2 + dy2 * dy2,
1070 c = dx3 * dx3 + dy3 * dy3;
1071 return Math.acos( (a + b - c) / _sqrt(4 * a * b) );
1072},
1073*/
1074//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)
1075
1076export function flatPointsToSegment(points, curviness) {
1077 if (curviness === void 0) {
1078 curviness = 1;
1079 }
1080
1081 var x = points[0],
1082 y = 0,
1083 segment = [x, y],
1084 i = 2;
1085
1086 for (; i < points.length; i += 2) {
1087 segment.push(x, y, points[i], y = (points[i] - x) * curviness / 2, x = points[i], -y);
1088 }
1089
1090 return segment;
1091} //points is an array of x/y points, like [x, y, x, y, x, y]
1092
1093export function pointsToSegment(points, curviness, cornerThreshold) {
1094 //points = simplifyPoints(points, tolerance);
1095 var l = points.length - 2,
1096 x = +points[0],
1097 y = +points[1],
1098 nextX = +points[2],
1099 nextY = +points[3],
1100 segment = [x, y, x, y],
1101 dx2 = nextX - x,
1102 dy2 = nextY - y,
1103 closed = Math.abs(points[l] - x) < 0.001 && Math.abs(points[l + 1] - y) < 0.001,
1104 prevX,
1105 prevY,
1106 angle,
1107 slope,
1108 i,
1109 dx1,
1110 dx3,
1111 dy1,
1112 dy3,
1113 d1,
1114 d2,
1115 a,
1116 b,
1117 c;
1118
1119 if (isNaN(cornerThreshold)) {
1120 cornerThreshold = Math.PI / 10;
1121 }
1122
1123 if (closed) {
1124 // 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)
1125 points.push(nextX, nextY);
1126 nextX = x;
1127 nextY = y;
1128 x = points[l - 2];
1129 y = points[l - 1];
1130 points.unshift(x, y);
1131 l += 4;
1132 }
1133
1134 curviness = curviness || curviness === 0 ? +curviness : 1;
1135
1136 for (i = 2; i < l; i += 2) {
1137 prevX = x;
1138 prevY = y;
1139 x = nextX;
1140 y = nextY;
1141 nextX = +points[i + 2];
1142 nextY = +points[i + 3];
1143 dx1 = dx2;
1144 dy1 = dy2;
1145 dx2 = nextX - x;
1146 dy2 = nextY - y;
1147 dx3 = nextX - prevX;
1148 dy3 = nextY - prevY;
1149 a = dx1 * dx1 + dy1 * dy1;
1150 b = dx2 * dx2 + dy2 * dy2;
1151 c = dx3 * dx3 + dy3 * dy3;
1152 angle = Math.acos((a + b - c) / _sqrt(4 * a * b)); //angle between the 3 points
1153
1154 d2 = angle / Math.PI * curviness; //temporary precalculation for speed (reusing d2 variable)
1155
1156 d1 = _sqrt(a) * d2; //the tighter the angle, the shorter we make the handles in proportion.
1157
1158 d2 *= _sqrt(b);
1159
1160 if (x !== prevX || y !== prevY) {
1161 if (angle > cornerThreshold) {
1162 slope = _atan2(dy3, dx3);
1163 segment.push(_round(x - _cos(slope) * d1), //first control point
1164 _round(y - _sin(slope) * d1), _round(x), //anchor
1165 _round(y), _round(x + _cos(slope) * d2), //second control point
1166 _round(y + _sin(slope) * d2));
1167 } else {
1168 slope = _atan2(dy1, dx1);
1169 segment.push(_round(x - _cos(slope) * d1), //first control point
1170 _round(y - _sin(slope) * d1));
1171 slope = _atan2(dy2, dx2);
1172 segment.push(_round(x), //anchor
1173 _round(y), _round(x + _cos(slope) * d2), //second control point
1174 _round(y + _sin(slope) * d2));
1175 }
1176 }
1177 }
1178
1179 segment.push(_round(nextX), _round(nextY), _round(nextX), _round(nextY));
1180
1181 if (closed) {
1182 segment.splice(0, 6);
1183 segment.length = segment.length - 6;
1184 }
1185
1186 return segment;
1187} //returns the squared distance between an x/y coordinate and a segment between x1/y1 and x2/y2
1188
1189function pointToSegDist(x, y, x1, y1, x2, y2) {
1190 var dx = x2 - x1,
1191 dy = y2 - y1,
1192 t;
1193
1194 if (dx || dy) {
1195 t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy);
1196
1197 if (t > 1) {
1198 x1 = x2;
1199 y1 = y2;
1200 } else if (t > 0) {
1201 x1 += dx * t;
1202 y1 += dy * t;
1203 }
1204 }
1205
1206 return Math.pow(x - x1, 2) + Math.pow(y - y1, 2);
1207}
1208
1209function simplifyStep(points, first, last, tolerance, simplified) {
1210 var maxSqDist = tolerance,
1211 firstX = points[first],
1212 firstY = points[first + 1],
1213 lastX = points[last],
1214 lastY = points[last + 1],
1215 index,
1216 i,
1217 d;
1218
1219 for (i = first + 2; i < last; i += 2) {
1220 d = pointToSegDist(points[i], points[i + 1], firstX, firstY, lastX, lastY);
1221
1222 if (d > maxSqDist) {
1223 index = i;
1224 maxSqDist = d;
1225 }
1226 }
1227
1228 if (maxSqDist > tolerance) {
1229 if (index - first > 2) {
1230 simplifyStep(points, first, index, tolerance, simplified);
1231 }
1232
1233 simplified.push(points[index], points[index + 1]);
1234
1235 if (last - index > 2) {
1236 simplifyStep(points, index, last, tolerance, simplified);
1237 }
1238 }
1239} //points is an array of x/y values like [x, y, x, y, x, y]
1240
1241
1242export function simplifyPoints(points, tolerance) {
1243 var prevX = parseFloat(points[0]),
1244 prevY = parseFloat(points[1]),
1245 temp = [prevX, prevY],
1246 l = points.length - 2,
1247 i,
1248 x,
1249 y,
1250 dx,
1251 dy,
1252 result,
1253 last;
1254 tolerance = Math.pow(tolerance || 1, 2);
1255
1256 for (i = 2; i < l; i += 2) {
1257 x = parseFloat(points[i]);
1258 y = parseFloat(points[i + 1]);
1259 dx = prevX - x;
1260 dy = prevY - y;
1261
1262 if (dx * dx + dy * dy > tolerance) {
1263 temp.push(x, y);
1264 prevX = x;
1265 prevY = y;
1266 }
1267 }
1268
1269 temp.push(parseFloat(points[l]), parseFloat(points[l + 1]));
1270 last = temp.length - 2;
1271 result = [temp[0], temp[1]];
1272 simplifyStep(temp, 0, last, tolerance, result);
1273 result.push(temp[last], temp[last + 1]);
1274 return result;
1275}
1276
1277function getClosestProgressOnBezier(iterations, px, py, start, end, slices, x0, y0, x1, y1, x2, y2, x3, y3) {
1278 var inc = (end - start) / slices,
1279 best = 0,
1280 t = start,
1281 x,
1282 y,
1283 d,
1284 dx,
1285 dy,
1286 inv;
1287 _bestDistance = _largeNum;
1288
1289 while (t <= end) {
1290 inv = 1 - t;
1291 x = inv * inv * inv * x0 + 3 * inv * inv * t * x1 + 3 * inv * t * t * x2 + t * t * t * x3;
1292 y = inv * inv * inv * y0 + 3 * inv * inv * t * y1 + 3 * inv * t * t * y2 + t * t * t * y3;
1293 dx = x - px;
1294 dy = y - py;
1295 d = dx * dx + dy * dy;
1296
1297 if (d < _bestDistance) {
1298 _bestDistance = d;
1299 best = t;
1300 }
1301
1302 t += inc;
1303 }
1304
1305 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;
1306}
1307
1308export function getClosestData(rawPath, x, y, slices) {
1309 //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)
1310 var closest = {
1311 j: 0,
1312 i: 0,
1313 t: 0
1314 },
1315 bestDistance = _largeNum,
1316 i,
1317 j,
1318 t,
1319 segment;
1320
1321 for (j = 0; j < rawPath.length; j++) {
1322 segment = rawPath[j];
1323
1324 for (i = 0; i < segment.length; i += 6) {
1325 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]);
1326
1327 if (bestDistance > _bestDistance) {
1328 bestDistance = _bestDistance;
1329 closest.j = j;
1330 closest.i = i;
1331 closest.t = t;
1332 }
1333 }
1334 }
1335
1336 return closest;
1337} //subdivide a Segment closest to a specific x,y coordinate
1338
1339export function subdivideSegmentNear(x, y, segment, slices, iterations) {
1340 var l = segment.length,
1341 bestDistance = _largeNum,
1342 bestT = 0,
1343 bestSegmentIndex = 0,
1344 t,
1345 i;
1346 slices = slices || 20;
1347 iterations = iterations || 3;
1348
1349 for (i = 0; i < l; i += 6) {
1350 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]);
1351
1352 if (bestDistance > _bestDistance) {
1353 bestDistance = _bestDistance;
1354 bestT = t;
1355 bestSegmentIndex = i;
1356 }
1357 }
1358
1359 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]);
1360 subdivideSegment(segment, bestSegmentIndex, t);
1361 return bestSegmentIndex + 6;
1362}
1363/*
1364Takes any of the following and converts it to an all Cubic Bezier SVG data string:
1365- A <path> data string like "M0,0 L2,4 v20,15 H100"
1366- A RawPath, like [[x, y, x, y, x, y, x, y][[x, y, x, y, x, y, x, y]]
1367- A Segment, like [x, y, x, y, x, y, x, y]
1368
1369Note: all numbers are rounded down to the closest 0.001 to minimize memory, maximize speed, and avoid odd numbers like 1e-13
1370*/
1371
1372export function rawPathToString(rawPath) {
1373 if (_isNumber(rawPath[0])) {
1374 //in case a segment is passed in instead
1375 rawPath = [rawPath];
1376 }
1377
1378 var result = "",
1379 l = rawPath.length,
1380 sl,
1381 s,
1382 i,
1383 segment;
1384
1385 for (s = 0; s < l; s++) {
1386 segment = rawPath[s];
1387 result += "M" + _round(segment[0]) + "," + _round(segment[1]) + " C";
1388 sl = segment.length;
1389
1390 for (i = 2; i < sl; i++) {
1391 result += _round(segment[i++]) + "," + _round(segment[i++]) + " " + _round(segment[i++]) + "," + _round(segment[i++]) + " " + _round(segment[i++]) + "," + _round(segment[i]) + " ";
1392 }
1393
1394 if (segment.closed) {
1395 result += "z";
1396 }
1397 }
1398
1399 return result;
1400}
1401/*
1402// 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.
1403export function cpCoordsToAngles(segment, copy) {
1404 var result = copy ? segment.slice(0) : segment,
1405 x, y, i;
1406 for (i = 0; i < segment.length; i+=6) {
1407 x = segment[i+2] - segment[i];
1408 y = segment[i+3] - segment[i+1];
1409 result[i+2] = Math.atan2(y, x);
1410 result[i+3] = Math.sqrt(x * x + y * y);
1411 x = segment[i+6] - segment[i+4];
1412 y = segment[i+7] - segment[i+5];
1413 result[i+4] = Math.atan2(y, x);
1414 result[i+5] = Math.sqrt(x * x + y * y);
1415 }
1416 return result;
1417}
1418
1419// 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.
1420export function cpAnglesToCoords(segment, copy) {
1421 var result = copy ? segment.slice(0) : segment,
1422 length = segment.length,
1423 rnd = 1000,
1424 angle, l, i, j;
1425 for (i = 0; i < length; i+=6) {
1426 angle = segment[i+2];
1427 l = segment[i+3]; //length
1428 result[i+2] = (((segment[i] + Math.cos(angle) * l) * rnd) | 0) / rnd;
1429 result[i+3] = (((segment[i+1] + Math.sin(angle) * l) * rnd) | 0) / rnd;
1430 angle = segment[i+4];
1431 l = segment[i+5]; //length
1432 result[i+4] = (((segment[i+6] - Math.cos(angle) * l) * rnd) | 0) / rnd;
1433 result[i+5] = (((segment[i+7] - Math.sin(angle) * l) * rnd) | 0) / rnd;
1434 }
1435 return result;
1436}
1437
1438//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.
1439export function populateSmoothData(rawPath) {
1440 let j = rawPath.length,
1441 smooth, segment, x, y, x2, y2, i, l, a, a2, isSmooth, smoothData;
1442 while (--j > -1) {
1443 segment = rawPath[j];
1444 isSmooth = segment.isSmooth = segment.isSmooth || [0, 0, 0, 0];
1445 smoothData = segment.smoothData = segment.smoothData || [0, 0, 0, 0];
1446 isSmooth.length = 4;
1447 l = segment.length - 2;
1448 for (i = 6; i < l; i += 6) {
1449 x = segment[i] - segment[i - 2];
1450 y = segment[i + 1] - segment[i - 1];
1451 x2 = segment[i + 2] - segment[i];
1452 y2 = segment[i + 3] - segment[i + 1];
1453 a = _atan2(y, x);
1454 a2 = _atan2(y2, x2);
1455 smooth = (Math.abs(a - a2) < 0.09);
1456 if (smooth) {
1457 smoothData[i - 2] = a;
1458 smoothData[i + 2] = a2;
1459 smoothData[i - 1] = _sqrt(x * x + y * y);
1460 smoothData[i + 3] = _sqrt(x2 * x2 + y2 * y2);
1461 }
1462 isSmooth.push(smooth, smooth, 0, 0, smooth, smooth);
1463 }
1464 //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.
1465 if (segment[l] === segment[0] && segment[l+1] === segment[1]) {
1466 x = segment[0] - segment[l-2];
1467 y = segment[1] - segment[l-1];
1468 x2 = segment[2] - segment[0];
1469 y2 = segment[3] - segment[1];
1470 a = _atan2(y, x);
1471 a2 = _atan2(y2, x2);
1472 if (Math.abs(a - a2) < 0.09) {
1473 smoothData[l-2] = a;
1474 smoothData[2] = a2;
1475 smoothData[l-1] = _sqrt(x * x + y * y);
1476 smoothData[3] = _sqrt(x2 * x2 + y2 * y2);
1477 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.
1478 }
1479 }
1480 }
1481 return rawPath;
1482}
1483export function pointToScreen(svgElement, point) {
1484 if (arguments.length < 2) { //by default, take the first set of coordinates in the path as the point
1485 let rawPath = getRawPath(svgElement);
1486 point = svgElement.ownerSVGElement.createSVGPoint();
1487 point.x = rawPath[0][0];
1488 point.y = rawPath[0][1];
1489 }
1490 return point.matrixTransform(svgElement.getScreenCTM());
1491}
1492
1493*/
\No newline at end of file