svg-path-simplify 0.4.4 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +9 -1
  3. package/dist/svg-path-simplify.esm.js +1649 -1205
  4. package/dist/svg-path-simplify.esm.min.js +2 -2
  5. package/dist/svg-path-simplify.js +1649 -1204
  6. package/dist/svg-path-simplify.min.js +2 -2
  7. package/dist/svg-path-simplify.pathdata.esm.js +301 -637
  8. package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
  9. package/dist/svg-path-simplify.poly.cjs +8 -9
  10. package/drawing.svg +62 -0
  11. package/index.html +13 -8
  12. package/package.json +1 -1
  13. package/src/index.js +3 -1
  14. package/src/pathData_get_intersections.js +777 -0
  15. package/src/pathData_offset.js +201 -0
  16. package/src/pathData_simplify_cubic.js +19 -21
  17. package/src/pathData_simplify_cubic_extrapolate.js +147 -8
  18. package/src/pathData_simplify_cubicsToArcs.js +65 -21
  19. package/src/pathSimplify-main.js +267 -64
  20. package/src/pathSimplify-presets.js +1 -1
  21. package/src/poly-fit-curve-schneider.js +171 -132
  22. package/src/poly-fit-curve-schneider_check_bulge.js +131 -0
  23. package/src/poly-offset.js +133 -0
  24. package/src/simplify_poly_RC.js +29 -7
  25. package/src/svg-getAttributes.js +1 -1
  26. package/src/svgii/geometry.js +130 -27
  27. package/src/svgii/geometry_bbox.js +1 -1
  28. package/src/svgii/geometry_length.js +1 -1
  29. package/src/svgii/pathData_analyze.js +6 -28
  30. package/src/svgii/pathData_convert.js +63 -15
  31. package/src/svgii/pathData_remove_collinear.js +32 -36
  32. package/src/svgii/pathData_reorder.js +3 -2
  33. package/src/svgii/pathData_simplify_refineExtremes.js +1 -3
  34. package/src/svgii/pathData_stringify.js +132 -6
  35. package/src/svgii/pathData_toPolygon.js +33 -23
  36. package/src/svgii/poly_analyze.js +272 -125
  37. package/src/svgii/poly_analyze_cleanup.js +311 -0
  38. package/src/svgii/poly_analyze_getTangents.js +125 -0
  39. package/src/svgii/poly_analyze_get_chunks.js +3 -1
  40. package/src/svgii/poly_normalize.js +21 -8
  41. package/src/svgii/poly_to_pathdata.js +477 -8
  42. package/src/svgii/rounding.js +35 -14
  43. package/src/svgii/svg_cleanup.js +44 -51
  44. package/src/svgii/svg_cleanup_convertPathLength.js +5 -0
  45. package/src/svgii/svg_cleanup_normalize_transforms.js +2 -2
  46. package/src/svgii/visualize.js +33 -2
  47. package/v/0.4.5/index.html +1040 -0
  48. package/v/0.4.5/svg-path-simplify.js +13227 -0
@@ -0,0 +1,777 @@
1
+ /**
2
+ * Find all intersections between two SVG paths.
3
+ * Based on snap.svg intersection function
4
+ * Inspired by https://github.com/bpmn-io/path-intersection
5
+ * lower sample distance = higher accuracy
6
+ */
7
+
8
+ import { pointAtT } from "./svgii/geometry";
9
+ import { parsePathDataNormalized } from "./svgii/pathData_convert";
10
+
11
+ // intersection from stringified path data
12
+ export function findPathIntersections(d1, d2, stopAtFirst = false, quality = 'medium') {
13
+
14
+ // parse and normalize
15
+ let options = {
16
+ toAbsolute: true,
17
+ arcsToCubic: true,
18
+ arcAccuracy: 1
19
+ }
20
+
21
+ // parse path data
22
+ let pathData1 = parsePathDataNormalized(d1, options);
23
+ let pathData2 = parsePathDataNormalized(d2, options);
24
+
25
+ //is congruent: return starting point
26
+ if (JSON.stringify(pathData1) === JSON.stringify(pathData2)) {
27
+ return [{ x: pathData1[0].values[0], y: pathData1[0].values[1] }]
28
+ }
29
+
30
+ return findPathDataIntersections(pathData1, pathData2, stopAtFirst, quality)
31
+
32
+ }
33
+
34
+
35
+
36
+
37
+ // intersection from parsed path data
38
+ export function findPathDataIntersections(pathData1, pathData2, stopAtFirst = false, quality = "medium") {
39
+
40
+ /**
41
+ * helpers
42
+ */
43
+ const findCommandIntersections = (data1, data2, xy, stopAtFirst = false, maxInter = 3) => {
44
+
45
+ let intersections = [];
46
+ let quit = false;
47
+ let scan = 0
48
+ for (let i = 0; i < data1.splits && !quit; i++) {
49
+
50
+ for (let j = 0; j < data2.splits && !quit; j++) {
51
+ let l1 = data1.dots[i],
52
+ l1_1 = data1.dots[i + 1],
53
+
54
+ l2 = data2.dots[j],
55
+ l2_1 = data2.dots[j + 1],
56
+ ci = Math.abs(l1_1.x - l1.x) < .01 ? 'y' : 'x',
57
+ cj = Math.abs(l2_1.x - l2.x) < .01 ? 'y' : 'x';
58
+
59
+ let intersection = intersectLines(l1, l1_1, l2, l2_1)
60
+ scan++
61
+
62
+ if (intersection) {
63
+ if (stopAtFirst && intersections) {
64
+ quit = true
65
+ }
66
+
67
+ let intersection_key = intersection.x.toFixed(1) + '_' + intersection.y.toFixed(1);
68
+
69
+ //if coorl1nates already found: skip
70
+ if (xy[intersection_key]) {
71
+ continue;
72
+ }
73
+ // save found intersection
74
+ xy[intersection_key] = true;
75
+
76
+ let t1 = l1.t + Math.abs((intersection[ci] - l1[ci]) / (l1_1[ci] - l1[ci])) * (l1_1.t - l1.t),
77
+ t2 = l2.t + Math.abs((intersection[cj] - l2[cj]) / (l2_1[cj] - l2[cj])) * (l2_1.t - l2.t);
78
+
79
+ if (t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1) {
80
+
81
+ intersections.push({
82
+ x: intersection.x,
83
+ y: intersection.y,
84
+ t1: t1,
85
+ t2: t2
86
+ });
87
+
88
+ // quit if max intersections were reached
89
+ if (intersections.length >= maxInter) {
90
+ //console.log('max intersections found', scan, intersections.length);
91
+ quit = true
92
+ }
93
+
94
+ }
95
+ }
96
+ }
97
+ }
98
+ return intersections;
99
+ }
100
+
101
+
102
+ const lineLength = (p0, p1) => {
103
+ return Math.sqrt(
104
+ (p1.x - p0.x) * (p1.x - p0.x) + (p1.y - p0.y) * (p1.y - p0.y)
105
+ );
106
+ }
107
+
108
+ const getPathDataBBox = (pathData) => {
109
+ // get segment bboxes
110
+ let allX = [];
111
+ let allY = [];
112
+ for (let i = 1; i < pathData.length; i++) {
113
+ let comPrev = pathData[i - 1];
114
+ let valuesPrev = comPrev.values;
115
+ let valuesPrevL = valuesPrev.length;
116
+ let p0 = { x: valuesPrev[valuesPrevL - 2], y: valuesPrev[valuesPrevL - 1] };
117
+
118
+ let com = pathData[i];
119
+ let { type, values } = com;
120
+ let valuesL = values.length;
121
+ let p = { x: values[valuesL - 2], y: values[valuesL - 1] };
122
+ let cp1, cp2, length, commandBBox;
123
+
124
+ let M = pathData[0].values;
125
+ cp1 = valuesL ? { x: values[0], y: values[1] } : M;
126
+ cp2 = type === 'C' ? { x: values[valuesL - 4], y: values[valuesL - 3] } : cp1;
127
+
128
+ // get approximated path bbox
129
+ if (valuesL) {
130
+ allX.push(p0.x, cp1.x, cp2.x, p.x);
131
+ allY.push(p0.y, cp1.y, cp2.y, p.y);
132
+ }
133
+ }
134
+
135
+ /**
136
+ * total bounding box
137
+ * (coarse approximation)
138
+ * are two paths remotely intersecting at all
139
+ */
140
+ let minX = Math.min(...allX);
141
+ let minY = Math.min(...allY);
142
+ let maxX = Math.max(...allX);
143
+ let maxY = Math.max(...allY);
144
+ let bb = { x: minX, y: minY, right: maxX, bottom: maxY };
145
+
146
+ return bb;
147
+ }
148
+
149
+
150
+
151
+ const isLine = (bez) => {
152
+ return (
153
+ bez[0] === bez[2] &&
154
+ bez[1] === bez[3] &&
155
+ bez[4] === bez[6] &&
156
+ bez[5] === bez[7]
157
+ );
158
+ }
159
+
160
+
161
+
162
+ const getBezierLineIntersection = (points, line) => {
163
+
164
+ /**
165
+ * based on:
166
+ * https://www.particleincell.com/2013/cubic-line-intersection/
167
+ */
168
+ const getIntersectionsCubic = (points, line) => {
169
+
170
+ let [p0, cp1, cp2, p] = points;
171
+
172
+ // adjust horizonzal or vertical start and end points
173
+ let epsilon = 0.0001;
174
+ p0.y = p0.y === p.y ? p0.y - epsilon : p0.y;
175
+ p0.x = p0.x === p.x ? p0.x - epsilon : p0.x;
176
+
177
+ const solveCubicRoots = (P) => {
178
+ let [a, b, c, d] = P;
179
+ var A = b / a,
180
+ B = c / a,
181
+ C = d / a,
182
+ Q = (3 * B - Math.pow(A, 2)) / 9,
183
+ R = (9 * A * B - 27 * C - 2 * Math.pow(A, 3)) / 54,
184
+ D = Math.pow(Q, 3) + Math.pow(R, 2),
185
+ Im;
186
+ let t = [];
187
+ if (D >= 0) { // complex or duplicate roots
188
+ const S = Math.sign(R + Math.sqrt(D)) * Math.pow(Math.abs(R + Math.sqrt(D)), (1 / 3));
189
+ const T = Math.sign(R - Math.sqrt(D)) * Math.pow(Math.abs(R - Math.sqrt(D)), (1 / 3));
190
+ t = [
191
+ -A / 3 + (S + T), // real root
192
+ -A / 3 - (S + T) / 2, // real part of complex root
193
+ -A / 3 - (S + T) / 2 // real part of complex root
194
+ ];
195
+ // complex part of root pair
196
+ Im = Math.abs(Math.sqrt(3) * (S - T) / 2);
197
+ // discard complex roots
198
+ if (Im !== 0) {
199
+ t[1] = -1;
200
+ t[2] = -1;
201
+ }
202
+ } else { // distinct real roots
203
+ let th = Math.acos(R / Math.sqrt(-Math.pow(Q, 3)));
204
+ t = [
205
+ 2 * Math.sqrt(-Q) * Math.cos(th / 3) - A / 3,
206
+ 2 * Math.sqrt(-Q) * Math.cos((th + 2 * Math.PI) / 3) - A / 3,
207
+ 2 * Math.sqrt(-Q) * Math.cos((th + 4 * Math.PI) / 3) - A / 3
208
+ ];
209
+ Im = 0.0;
210
+ }
211
+ // discard out of spec roots
212
+ return t.filter(val => { return val >= 0 && val <= 1 })
213
+ }
214
+
215
+ const bezierCoefficients = (p0, p1, p2, p3) => {
216
+ return [
217
+ -p0 + 3 * p1 + -3 * p2 + p3,
218
+ 3 * p0 - 6 * p1 + 3 * p2,
219
+ -3 * p0 + 3 * p1,
220
+ p0
221
+ ];
222
+ }
223
+
224
+
225
+ let A = line[1].y - line[0].y;
226
+ let B = line[0].x - line[1].x;
227
+ let C = line[0].x * (line[0].y - line[1].y) + line[0].y * (line[1].x - line[0].x);
228
+
229
+ const xBezierCoefficients = bezierCoefficients(p0.x, cp1.x, cp2.x, p.x);
230
+ const yBezierCoefficients = bezierCoefficients(p0.y, cp1.y, cp2.y, p.y);
231
+ const P = [
232
+ A * xBezierCoefficients[0] + B * yBezierCoefficients[0],
233
+ A * xBezierCoefficients[1] + B * yBezierCoefficients[1],
234
+ A * xBezierCoefficients[2] + B * yBezierCoefficients[2],
235
+ A * xBezierCoefficients[3] + B * yBezierCoefficients[3] + C
236
+ ];
237
+ let cubicRoots = solveCubicRoots(P)
238
+
239
+ let pts = [];
240
+ cubicRoots.forEach(t => {
241
+ let pt = {
242
+ x: xBezierCoefficients[0] * t ** 3 + xBezierCoefficients[1] * t ** 2 + xBezierCoefficients[2] * t + xBezierCoefficients[3],
243
+ y: yBezierCoefficients[0] * t ** 3 + yBezierCoefficients[1] * t ** 2 + yBezierCoefficients[2] * t + yBezierCoefficients[3]
244
+ }
245
+
246
+ // calc t for lineto
247
+ let pt1 = { x: pt.x - 10, y: pt.y - 10 }
248
+ let pt2 = { x: pt.x + 10, y: pt.y + 10 }
249
+ let inter = intersectLines(pt1, pt2, line[0], line[1])
250
+
251
+ pts.push(
252
+ {
253
+ x: pt.x,
254
+ y: pt.y,
255
+ t1: inter.t,
256
+ t2: t
257
+ })
258
+ })
259
+
260
+ return pts;
261
+ }
262
+
263
+
264
+
265
+ /**
266
+ * Based on
267
+ * https://stackoverflow.com/questions/77003429/intersection-of-quadratic-bezier-path-and-line/77010181?noredirect=1#comment135763117_77010181
268
+ */
269
+ const getIntersectionsQuadratic = (points, line) => {
270
+ const { atan2, cos, sin, sqrt } = Math;
271
+
272
+
273
+ const getRoots = (pts, [[x1, y1], [x2, y2]]) => {
274
+ // Transform and rotate our coordinates as per above,
275
+ // noting that we only care about the y coordinate:
276
+ const angle = atan2(y2 - y1, x2 - x1);
277
+ const v = pts.map(
278
+ ([x, y]) => (x - x1) * sin(-angle) + (y - y1) * cos(-angle)
279
+ );
280
+ // And now we're essentially done, we can trivially find our roots:
281
+ return (
282
+ solveQuadratic(v[0], v[1], v[2])
283
+ // ...as long as those roots are in the Bezier interval [0,1] of course.
284
+ .filter((t) => 0 <= t && t <= 1)
285
+ );
286
+ }
287
+
288
+ const solveQuadratic = (v1, v2, v3) => {
289
+ const a = v1 - 2 * v2 + v3,
290
+ b = 2 * (v2 - v1),
291
+ c = v1;
292
+ // quick check, is "a" zero? if so, the solution is linear.
293
+ if (a === 0) return b === 0 ? [] : [-c / b];
294
+ const u = -b / (2 * a),
295
+ v = b ** 2 - 4 * a * c;
296
+ if (v < 0) return []; // If there are no roots, there are no roots. Done.
297
+ if (v === 0) return [u]; // If there's only one root, return that.
298
+ // And if there are two roots we compute the "full" formula
299
+ const w = sqrt(v) / (2 * a);
300
+ return [u + w, u - w];
301
+ }
302
+
303
+ // flatten to coordinate array
304
+ points = points.map(pt => { return Object.values(pt) });
305
+ let lineArr = line.map(pt => { return Object.values(pt) });
306
+ const [[x1, y1], [x2, y2], [x3, y3]] = points;
307
+ const roots = getRoots(points, lineArr);
308
+
309
+ const coordForRoot = (t, x2, x3, y2, y3) => {
310
+ const mt = 1 - t;
311
+ let pt = {
312
+ x: x1 * mt ** 2 + 2 * x2 * t * mt + x3 * t ** 2,
313
+ y: y1 * mt ** 2 + 2 * y2 * t * mt + y3 * t ** 2,
314
+ }
315
+
316
+ // calc t for lineto
317
+ let pt1 = { x: pt.x - 10, y: pt.y - 10 }
318
+ let pt2 = { x: pt.x + 10, y: pt.y + 10 }
319
+ let inter = intersectLines(pt1, pt2, line[0], line[1])
320
+
321
+ return {
322
+ x: pt.x,
323
+ y: pt.y,
324
+ t1: inter.t,
325
+ t2: t
326
+ };
327
+ }
328
+
329
+ const intersections = roots.map((t) => coordForRoot(t, x2, x3, y2, y3));
330
+ return intersections;
331
+ }
332
+
333
+ let inter = points.length === 4 ? getIntersectionsCubic(points, line) : getIntersectionsQuadratic(points, line)
334
+ return inter;
335
+ }
336
+
337
+
338
+
339
+
340
+
341
+
342
+
343
+ // all results
344
+ let res = [];
345
+
346
+ //no path intersection at all - exit
347
+ let bb1 = getPathDataBBox(pathData1)
348
+ let bb2 = getPathDataBBox(pathData2)
349
+
350
+ if (!isBBoxIntersect(bb1, bb2)) {
351
+ return res
352
+ }
353
+
354
+ // collect found intersections to exclude duplicates close points
355
+ let xy = {}
356
+
357
+
358
+
359
+ function addSegmentDots(obj, quality = "medium") {
360
+ let { cpts } = obj;
361
+ let l = 240, sampleDist = 20, div = 16, minDiv = 16, maxDiv = 16;
362
+
363
+ if (quality === "medium" || quality === "high") {
364
+ // approximate length by polygon length
365
+ let pM = pointAtT(cpts, 0.5)
366
+ l = lineLength(cpts[0], pM) + lineLength(cpts[cpts.length - 1], pM);
367
+ sampleDist = quality === "medium" ? 10 : 5
368
+ div = Math.ceil(l / sampleDist)
369
+ minDiv = quality === "high" ? 48 : 24;
370
+ maxDiv = quality === "high" ? 1000 : 500;
371
+ }
372
+
373
+ // set minimum and maximum precision for short or long segments
374
+ let splits = cpts.length === 2 || isLine(cpts) ? 1 : (div > minDiv ? div : (div > maxDiv ? maxDiv : minDiv)) || 1
375
+
376
+ //console.log(sampleDist, maxDiv, splits);
377
+
378
+ obj.len = l
379
+ obj.splits = splits
380
+ obj.bb = commandBBox(cpts)
381
+
382
+ for (let i = 0; i < splits + 1; i++) {
383
+ let t = i / splits
384
+ let pt = pointAtT(cpts, t);
385
+ obj.dots.push({ x: pt.x, y: pt.y, t: t });
386
+ }
387
+ }
388
+
389
+ let pathInfo1 = getPathInfo(pathData1)
390
+ let pathInfo2 = getPathInfo(pathData2)
391
+ let quit = false;
392
+
393
+ // check segment intersections
394
+ for (let i = 0; i < pathData1.length && !quit; i++) {
395
+
396
+ //measure paths
397
+ let data1 = pathInfo1[i];
398
+
399
+ if (!data1.cpts.length) {
400
+ continue
401
+ }
402
+
403
+ for (var j = 0; j < pathData2.length && !quit; j++) {
404
+ let data2 = pathInfo2[j]
405
+ if (!data2.cpts.length) {
406
+ continue
407
+ }
408
+
409
+ if (isBBoxIntersect(data1.bb, data2.bb)) {
410
+
411
+ let type1 = data1.type, type2 = data2.type;
412
+ let maxInter = type1 === 'C' && type2 === 'C' ? 4 : (type1 === 'L' && type2 === 'L' ? 1 : 2)
413
+
414
+ /**
415
+ * 1. line vs bezier
416
+ */
417
+ let useBezCalc = true;
418
+ if ((type1 === 'L' || type2 === 'L') && (data1.cpts.length > 2 || data2.cpts.length > 2) && useBezCalc) {
419
+ let line = type1 === 'L' ? data1 : data2;
420
+ let bez = type1 === 'L' ? data2 : data1;
421
+
422
+ let inter = getBezierLineIntersection(bez.cpts, line.cpts);
423
+ if (inter.length) {
424
+ inter.forEach(item => {
425
+ let it = {
426
+ cpts1: line.cpts,
427
+ cpts2: bez.cpts,
428
+ segment1: i,
429
+ segment2: i,
430
+ t1: item.t1,
431
+ t2: item.t2,
432
+ x: item.x,
433
+ y: item.y,
434
+ }
435
+ res.push(it)
436
+ })
437
+
438
+ continue;
439
+ }
440
+
441
+ }
442
+
443
+
444
+ //self intersecting cubic
445
+ if (maxInter === 4) {
446
+ if (!data1.selfintersects) {
447
+ maxInter--
448
+ }
449
+ if (!data2.selfintersects) {
450
+ maxInter--
451
+ }
452
+ }
453
+
454
+ // segment intersection - calculate sample points
455
+ if (!data1.dots.length) {
456
+ addSegmentDots(data1, quality)
457
+ }
458
+ if (!data2.dots.length) {
459
+ addSegmentDots(data2, quality)
460
+ }
461
+
462
+ let intersections = findCommandIntersections(data1, data2, xy, stopAtFirst, maxInter);
463
+
464
+ if (stopAtFirst && intersections.length) {
465
+ quit = true
466
+ }
467
+
468
+ for (let k = 0; k < intersections.length; k++) {
469
+ intersections[k].segment1 = i;
470
+ intersections[k].segment2 = j;
471
+ intersections[k].cpts1 = data1.cpts;
472
+ intersections[k].cpts2 = data2.cpts;
473
+ }
474
+ res = res.concat(intersections);
475
+ }
476
+ }
477
+ }
478
+ return res;
479
+ }
480
+
481
+
482
+
483
+
484
+
485
+ /**
486
+ * based on: https://stackoverflow.com/questions/12219802/a-javascript-function-that-returns-the-x-y-points-of-intersection-between-two-ci
487
+ */
488
+
489
+ function findCircleIntersection(c1, c2, decimals = 8) {
490
+
491
+ // Start constructing the response object.
492
+ let result = {
493
+ intersect_count: 0,
494
+ intersect_occurs: false,
495
+ one_is_in_other: false,
496
+ are_equal: false,
497
+ points: []
498
+ };
499
+
500
+ // Get vertical and horizontal distances between circles.
501
+ const dx = c2.cx - c1.cx;
502
+ const dy = c2.cy - c1.cy;
503
+
504
+ // Calculate the distance between the circle centers as a straight line.
505
+ const dist = Math.hypot(dy, dx);
506
+
507
+ // Check if circles are the same.
508
+ if (c1.cx === c2.cx && c1.cy === c2.cy && c1.r === c2.r) {
509
+ result.are_equal = true;
510
+ return [];
511
+ }
512
+
513
+ // Check one circle isn't inside the other.
514
+ let rDiff = Math.abs(c1.r - c2.r);
515
+ if (dist <= rDiff) {
516
+ result.one_is_in_other = true;
517
+ if (dist < rDiff) return [];
518
+ }
519
+
520
+ // Check if circles intersect.
521
+ if (dist <= c1.r + c2.r) {
522
+ result.intersect_occurs = true;
523
+ }
524
+
525
+ // Find the intersection points
526
+ if (result.intersect_occurs) {
527
+ // Centroid is the pt where two lines cross. A line between the circle centers
528
+ // and a line between the intersection points.
529
+ const centroid = (c1.r * c1.r - c2.r * c2.r + dist * dist) / (2 * dist);
530
+
531
+ // Get the coordinates of centroid.
532
+ const x2 = c1.cx + (dx * centroid) / dist;
533
+ const y2 = c1.cy + (dy * centroid) / dist;
534
+
535
+ // Get the distance from centroid to the intersection points.
536
+ const h = Math.sqrt(c1.r * c1.r - centroid * centroid);
537
+
538
+ // Get the x and y dist of the intersection points from centroid.
539
+ const rx = -dy * (h / dist);
540
+ const ry = dx * (h / dist);
541
+
542
+ let pt1 = {
543
+ x: +(x2 + rx).toFixed(decimals),
544
+ y: +(y2 + ry).toFixed(decimals)
545
+ };
546
+ let pt2 = {
547
+ x: +(x2 - rx).toFixed(decimals),
548
+ y: +(y2 - ry).toFixed(decimals)
549
+ };
550
+
551
+ // Add intersection count to results
552
+ if (pt1.x === pt2.x && pt1.y === pt2.y) {
553
+ result.intersect_count = 1;
554
+ result.points.push(pt1);
555
+ } else {
556
+ result.intersect_count = 2;
557
+ result.points.push(pt1, pt2);
558
+ }
559
+ }
560
+ //return result;
561
+ return result.points;
562
+ }
563
+
564
+
565
+
566
+ function intersectLines(p1, p2, p3, p4) {
567
+
568
+ const isOnLine = (x1, y1, x2, y2, px, py, tolerance = 0.001) => {
569
+ var f = function (somex) { return (y2 - y1) / (x2 - x1) * (somex - x1) + y1; };
570
+ return Math.abs(f(px) - py) < tolerance
571
+ && px >= x1 && px <= x2;
572
+ }
573
+
574
+ if (
575
+ Math.max(p1.x, p2.x) < Math.min(p3.x, p4.x) ||
576
+ Math.min(p1.x, p2.x) > Math.max(p3.x, p4.x) ||
577
+ Math.max(p1.y, p2.y) < Math.min(p3.y, p4.y) ||
578
+ Math.min(p1.y, p2.y) > Math.max(p3.y, p4.y)
579
+ ) {
580
+ return false;
581
+ }
582
+
583
+ let denominator = (p1.x - p2.x) * (p3.y - p4.y) - (p1.y - p2.y) * (p3.x - p4.x);
584
+ if (denominator == 0) {
585
+ return false;
586
+ }
587
+
588
+ let a = p1.y - p3.y;
589
+ let b = p1.x - p3.x;
590
+ let numerator1 = ((p4.x - p3.x) * a) - ((p4.y - p3.y) * b);
591
+ let numerator2 = ((p2.x - p1.x) * a) - ((p2.y - p1.y) * b);
592
+ a = numerator1 / denominator;
593
+ b = numerator2 / denominator;
594
+
595
+
596
+ let px = p1.x + (a * (p2.x - p1.x)),
597
+ py = p1.y + (a * (p2.y - p1.y));
598
+
599
+ let px2 = +px.toFixed(2),
600
+ py2 = +py.toFixed(2);
601
+
602
+
603
+ // is point in boundaries/actually on line?
604
+ if (
605
+ px2 < +Math.min(p1.x, p2.x).toFixed(2) ||
606
+ px2 > +Math.max(p1.x, p2.x).toFixed(2) ||
607
+ px2 < +Math.min(p3.x, p4.x).toFixed(2) ||
608
+ px2 > +Math.max(p3.x, p4.x).toFixed(2) ||
609
+ py2 < +Math.min(p1.y, p2.y).toFixed(2) ||
610
+ py2 > +Math.max(p1.y, p2.y).toFixed(2) ||
611
+ py2 < +Math.min(p3.y, p4.y).toFixed(2) ||
612
+ py2 > +Math.max(p3.y, p4.y).toFixed(2)
613
+ ) {
614
+
615
+ // if final point is on line
616
+ if (isOnLine(p3.x, p3.y, p4.x, p4.y, p2.x, p2.y, 0.1)) {
617
+ return { x: p2.x, y: p2.y };
618
+ }
619
+ return false;
620
+ }
621
+ return { x: px, y: py, t: b };
622
+ }
623
+
624
+
625
+
626
+ function isBBoxIntersect(bbox1, bbox2) {
627
+ let { x, y, right, bottom } = bbox1;
628
+ let [x2, y2, right2, bottom2] = [bbox2.x, bbox2.y, bbox2.right, bbox2.bottom];
629
+
630
+ let bboxIntersection =
631
+ x <= right2 &&
632
+ y <= bottom2 &&
633
+ bottom >= y2 &&
634
+ right >= x2 ?
635
+ true :
636
+ false;
637
+
638
+ return bboxIntersection;
639
+ }
640
+
641
+
642
+ function commandBBox(points) {
643
+ let allX = points.map(pt => { return pt.x })
644
+ let allY = points.map(pt => { return pt.y })
645
+
646
+ let minX = Math.min(...allX);
647
+ let maxX = Math.max(...allX);
648
+ let minY = Math.min(...allY);
649
+ let maxY = Math.max(...allY);
650
+
651
+ let bb = {
652
+ x: minX,
653
+ y: minY,
654
+ width: maxX - minX,
655
+ height: maxY - minY,
656
+ right: maxX,
657
+ bottom: maxY
658
+ }
659
+ //console.log(bb);
660
+ return bb;
661
+ }
662
+
663
+
664
+ function getPolyIntersections(el1, el2) {
665
+ function arrayToPoints(pts) {
666
+ let ptsN = []
667
+ for (let i = 1; i < pts.length; i += 2) {
668
+ ptsN.push({ x: pts[i - 1], y: pts[i] })
669
+ }
670
+ return ptsN
671
+ }
672
+
673
+ let pts1 = el1.type === 'line' ? [el1.x1, el1.y1, el1.x2, el1.y2] : el1.points
674
+ pts1 = arrayToPoints(pts1)
675
+
676
+ // close to start
677
+ if (el1.type === 'polygon') pts1.push(pts1[0])
678
+
679
+ let pts2 = el2.type === 'line' ? [el2.x1, el2.y1, el2.x2, el2.y2] : el2.points
680
+ pts2 = arrayToPoints(pts2);
681
+ if (el2.type === 'polygon') pts2.push(pts2[0])
682
+
683
+
684
+ let intersections = []
685
+ for (let i = 0; i < pts1.length - 1; i++) {
686
+
687
+ let l1 = pts1[i]
688
+ let l1_1 = pts1[i + 1] ? pts1[i + 1] : l1
689
+
690
+ for (let j = 0; j < pts2.length - 1; j++) {
691
+
692
+ let l2 = pts2[j]
693
+ let l2_1 = pts2[j + 1] ? pts2[j + 1] : l2
694
+
695
+ let intersection = intersectLines(l1, l1_1, l2, l2_1)
696
+ if (intersection) {
697
+ intersections.push(intersection)
698
+ }
699
+
700
+ }
701
+ }
702
+ return intersections
703
+
704
+ }
705
+
706
+
707
+
708
+
709
+
710
+ function getPathInfo(pathData) {
711
+ let pathArr = [];
712
+ let M = { x: pathData[0].values[0], y: pathData[0].values[1] }
713
+
714
+ pathData.forEach((com, i) => {
715
+ let cpts = [];
716
+ let { type, values } = com;
717
+ let obj = {
718
+ type: type,
719
+ cpts: [],
720
+ selfintersects: false,
721
+ len: 0,
722
+ splits: 0,
723
+ dots: [],
724
+ bb: {}
725
+ };
726
+
727
+ let valuesL = values.length;
728
+ let comPrev = i > 0 ? pathData[i - 1] : pathData[i]
729
+ let valuesPrev = comPrev.values
730
+ let valuesPrevL = valuesPrev.length
731
+
732
+
733
+ let p0 = { x: valuesPrev[valuesPrevL - 2], y: valuesPrev[valuesPrevL - 1] }
734
+ let p = valuesL ? { x: values[valuesL - 2], y: values[valuesL - 1] } : M;
735
+ let cp1 = valuesL ? { x: values[0], y: values[1] } : p0;
736
+ let cp2 = valuesL > 2 ? { x: values[2], y: values[3] } : p;
737
+
738
+
739
+ switch (type) {
740
+ // new M
741
+ case 'M':
742
+ M = { x: p.x, y: p.y };
743
+ cpts = [];
744
+ break;
745
+ case 'C':
746
+ cpts = [p0, cp1, cp2, p];
747
+ obj.selfintersects = intersectLines(p0, cp1, cp2, p) !== false ? true : false;
748
+ break;
749
+
750
+ case 'Q':
751
+ cpts = [p0, cp1, p];
752
+ break;
753
+
754
+ // treat Z as lineto
755
+ case 'Z':
756
+ case 'z':
757
+ p = M
758
+ cpts = [p0, p]
759
+ break;
760
+ default:
761
+ cpts = [p0, p];
762
+ }
763
+
764
+ if (cpts.length) {
765
+
766
+ obj.cpts = cpts
767
+ obj.bb = commandBBox(cpts)
768
+
769
+ }
770
+ pathArr.push(obj)
771
+ })
772
+
773
+
774
+ return pathArr
775
+ }
776
+
777
+