svg-path-simplify 0.1.3 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/svg-path-simplify.esm.js +3905 -1533
- package/dist/svg-path-simplify.esm.min.js +13 -1
- package/dist/svg-path-simplify.js +3923 -1551
- package/dist/svg-path-simplify.min.js +13 -1
- package/dist/svg-path-simplify.min.js.gz +0 -0
- package/index.html +61 -31
- package/package.json +3 -5
- package/src/constants.js +3 -0
- package/src/index-node.js +0 -1
- package/src/index.js +26 -0
- package/src/pathData_simplify_cubic.js +74 -31
- package/src/pathData_simplify_cubicsToArcs.js +566 -0
- package/src/pathData_simplify_harmonize_cpts.js +170 -0
- package/src/pathData_simplify_revertToquadratics.js +21 -0
- package/src/pathSimplify-main.js +253 -86
- package/src/poly-fit-curve-schneider.js +570 -0
- package/src/simplify_poly_RDP.js +146 -0
- package/src/simplify_poly_radial_distance.js +100 -0
- package/src/svg_getViewbox.js +1 -1
- package/src/svgii/geometry.js +389 -63
- package/src/svgii/geometry_area.js +2 -1
- package/src/svgii/pathData_analyze.js +259 -212
- package/src/svgii/pathData_convert.js +91 -663
- package/src/svgii/pathData_fromPoly.js +12 -0
- package/src/svgii/pathData_parse.js +90 -89
- package/src/svgii/pathData_parse_els.js +3 -0
- package/src/svgii/pathData_parse_fontello.js +449 -0
- package/src/svgii/pathData_remove_collinear.js +44 -37
- package/src/svgii/pathData_reorder.js +2 -1
- package/src/svgii/pathData_simplify_redraw.js +343 -0
- package/src/svgii/pathData_simplify_refineCorners.js +18 -9
- package/src/svgii/pathData_simplify_refineExtremes.js +19 -78
- package/src/svgii/pathData_split.js +42 -45
- package/src/svgii/pathData_toPolygon.js +130 -4
- package/src/svgii/poly_analyze.js +470 -14
- package/src/svgii/poly_to_pathdata.js +224 -19
- package/src/svgii/rounding.js +55 -112
- package/src/svgii/svg_cleanup.js +13 -1
- package/src/svgii/visualize.js +8 -3
- package/{debug.cjs → tests/debug.cjs} +3 -0
- /package/{test.js → tests/test.js} +0 -0
- /package/{testSVG.js → tests/testSVG.js} +0 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* radialDistance simplification
|
|
4
|
+
* sloppy but fast
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getDistManhattan, getSquareDistance, reducePoints } from "./svgii/geometry";
|
|
8
|
+
import { getPolyBBox } from "./svgii/geometry_bbox";
|
|
9
|
+
import { renderPoint } from "./svgii/visualize";
|
|
10
|
+
|
|
11
|
+
export function simplifyRD(pts, {
|
|
12
|
+
quality = 0.9,
|
|
13
|
+
width = 0,
|
|
14
|
+
height = 0,
|
|
15
|
+
absolute = false,
|
|
16
|
+
// use square or manhattan distances
|
|
17
|
+
manhattan = false,
|
|
18
|
+
exclude = []
|
|
19
|
+
} = {}) {
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* switch between absolute or
|
|
23
|
+
* quality based relative thresholds
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
if (typeof quality === 'string') {
|
|
27
|
+
let value = parseFloat(quality);
|
|
28
|
+
absolute = true;
|
|
29
|
+
quality = value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// nothing to do - exit
|
|
33
|
+
if (pts.length < 4 || (!absolute && quality) >= 1) return pts;
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
// convert quality to squaredistance tolerance
|
|
37
|
+
let tolerance = quality;
|
|
38
|
+
|
|
39
|
+
if (!absolute) {
|
|
40
|
+
|
|
41
|
+
// quality to tolerance
|
|
42
|
+
tolerance = 1 - quality;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* approximate dimensions
|
|
46
|
+
* adjust tolerance for
|
|
47
|
+
* very small polygons e.g geodata
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
if (!width && !height) {
|
|
51
|
+
let polyS = reducePoints(pts, 12);
|
|
52
|
+
({ width, height } = getPolyBBox(polyS));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!manhattan) {
|
|
56
|
+
// average side lengths
|
|
57
|
+
let dimAvg = (width + height) / 2;
|
|
58
|
+
let scale = dimAvg / 25;
|
|
59
|
+
tolerance = (tolerance * (scale)) ** 2
|
|
60
|
+
} else {
|
|
61
|
+
// use manhattan
|
|
62
|
+
tolerance = (width + height) * 0.05 * (1 - quality)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let p0 = pts[0];
|
|
68
|
+
let pt;
|
|
69
|
+
|
|
70
|
+
// new simplified point array
|
|
71
|
+
let ptsSmp = [p0];
|
|
72
|
+
let l = pts.length
|
|
73
|
+
let lenExclude = exclude.length;
|
|
74
|
+
let dist = 0
|
|
75
|
+
|
|
76
|
+
for (let i = 1; i < l; i++) {
|
|
77
|
+
pt = pts[i];
|
|
78
|
+
|
|
79
|
+
// skip
|
|
80
|
+
dist = manhattan ? getDistManhattan(p0, pt) : getSquareDistance(p0, pt);
|
|
81
|
+
|
|
82
|
+
if ( dist < tolerance && (!lenExclude || !exclude.includes(i)) ) {
|
|
83
|
+
p0 = pt;
|
|
84
|
+
continue
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
ptsSmp.push(pt);
|
|
88
|
+
p0 = pt;
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// add last point - if not coinciding with first point
|
|
93
|
+
if (p0.x !== pt.x && p0.y !== pt.y) {
|
|
94
|
+
ptsSmp.push(pt);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
//console.log('ptsSmp', ptsSmp, l);
|
|
98
|
+
return ptsSmp;
|
|
99
|
+
|
|
100
|
+
}
|
package/src/svg_getViewbox.js
CHANGED
package/src/svgii/geometry.js
CHANGED
|
@@ -3,6 +3,9 @@ import {abs, acos, asin, atan, atan2, ceil, cos, exp, floor,
|
|
|
3
3
|
log, max, min, pow, random, round, sin, sqrt, tan, PI} from '/.constants.js';
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { rad2Deg } from "../constants";
|
|
7
|
+
import { renderPoint } from "./visualize";
|
|
8
|
+
|
|
6
9
|
export const {
|
|
7
10
|
abs, acos, asin, atan, atan2, ceil, cos, exp, floor,
|
|
8
11
|
log, max, min, pow, random, round, sin, sqrt, tan, PI
|
|
@@ -18,6 +21,47 @@ export function getAngle(p1, p2, normalize = false) {
|
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
|
|
24
|
+
export function getDeltaAngle2(centerPoint, startPoint, endPoint, largeArc = false) {
|
|
25
|
+
|
|
26
|
+
const normalizeAngle = (angle) => {
|
|
27
|
+
let normalized = angle % (2 * Math.PI);
|
|
28
|
+
|
|
29
|
+
if (normalized > Math.PI) {
|
|
30
|
+
normalized -= 2 * Math.PI;
|
|
31
|
+
} else if (normalized <= -Math.PI) {
|
|
32
|
+
normalized += 2 * Math.PI;
|
|
33
|
+
}
|
|
34
|
+
return normalized;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let startAngle = getAngle(centerPoint, startPoint, false)
|
|
38
|
+
|
|
39
|
+
let endAngle = getAngle(centerPoint, endPoint, false)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
// Calculate raw delta angle (difference)
|
|
43
|
+
let deltaAngle = endAngle - startAngle;
|
|
44
|
+
|
|
45
|
+
// Normalize the delta angle to range (-π, π]
|
|
46
|
+
//deltaAngle = normalizeAngle(deltaAngle);
|
|
47
|
+
|
|
48
|
+
//if (largeArc) deltaAngle = Math.PI * 2 - Math.abs(deltaAngle);
|
|
49
|
+
|
|
50
|
+
let phi = 180 / Math.PI
|
|
51
|
+
let startAngleDeg = startAngle * phi
|
|
52
|
+
let endAngleDeg = endAngle * phi
|
|
53
|
+
let deltaAngleDeg = deltaAngle * phi
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
startAngle, endAngle, deltaAngle, startAngleDeg,
|
|
57
|
+
endAngleDeg,
|
|
58
|
+
deltaAngleDeg
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
21
65
|
export function getDeltaAngle(centerPoint, startPoint, endPoint, largeArc = false) {
|
|
22
66
|
|
|
23
67
|
const normalizeAngle = (angle) => {
|
|
@@ -47,7 +91,7 @@ export function getDeltaAngle(centerPoint, startPoint, endPoint, largeArc = fals
|
|
|
47
91
|
// Normalize the delta angle to range (-π, π]
|
|
48
92
|
deltaAngle = normalizeAngle(deltaAngle);
|
|
49
93
|
|
|
50
|
-
if (largeArc) deltaAngle = Math.PI*2 - Math.abs(deltaAngle);
|
|
94
|
+
if (largeArc) deltaAngle = Math.PI * 2 - Math.abs(deltaAngle);
|
|
51
95
|
|
|
52
96
|
let phi = 180 / Math.PI
|
|
53
97
|
let startAngleDeg = startAngle * phi
|
|
@@ -127,25 +171,7 @@ export function checkLineIntersection(p1 = null, p2 = null, p3 = null, p4 = null
|
|
|
127
171
|
|
|
128
172
|
|
|
129
173
|
|
|
130
|
-
/**
|
|
131
|
-
* get distance between 2 points
|
|
132
|
-
* pythagorean theorem
|
|
133
|
-
*/
|
|
134
|
-
export function getDistance(p1, p2) {
|
|
135
|
-
return sqrt(
|
|
136
|
-
(p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y)
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
174
|
|
|
140
|
-
export function getSquareDistance(p1, p2) {
|
|
141
|
-
return (p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export function lineLength(p1, p2) {
|
|
145
|
-
return sqrt(
|
|
146
|
-
(p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y)
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
175
|
|
|
150
176
|
|
|
151
177
|
/**
|
|
@@ -169,7 +195,7 @@ export function interpolate(p1, p2, t, getTangent = false) {
|
|
|
169
195
|
}
|
|
170
196
|
|
|
171
197
|
|
|
172
|
-
export function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false) {
|
|
198
|
+
export function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false, returnArray = false) {
|
|
173
199
|
|
|
174
200
|
const getPointAtBezierT = (pts, t, getTangent = false) => {
|
|
175
201
|
|
|
@@ -261,6 +287,15 @@ export function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false) {
|
|
|
261
287
|
|
|
262
288
|
}
|
|
263
289
|
|
|
290
|
+
|
|
291
|
+
// normalize if input was array not pt object
|
|
292
|
+
if (Array.isArray(pts[0])) {
|
|
293
|
+
pts = pts.map(pt => { return { x: pt[0], y: pt[1] } })
|
|
294
|
+
// also output array if not explicitely defined
|
|
295
|
+
returnArray = true
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
|
|
264
299
|
let pt;
|
|
265
300
|
if (pts.length > 2) {
|
|
266
301
|
pt = getPointAtBezierT(pts, t, getTangent);
|
|
@@ -273,7 +308,7 @@ export function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false) {
|
|
|
273
308
|
// normalize negative angles
|
|
274
309
|
if (getTangent && pt.angle < 0) pt.angle += PI * 2
|
|
275
310
|
|
|
276
|
-
return pt
|
|
311
|
+
return returnArray ? [pt.x, pt.y] : pt
|
|
277
312
|
}
|
|
278
313
|
|
|
279
314
|
|
|
@@ -392,8 +427,8 @@ export function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, swe
|
|
|
392
427
|
let phi = rx === ry ? 0 : (xAxisRotation * PI) / 180;
|
|
393
428
|
let cx, cy
|
|
394
429
|
|
|
395
|
-
let s_phi = !phi ? 0 : sin(phi);
|
|
396
|
-
let c_phi = !phi ? 1 : cos(phi);
|
|
430
|
+
let s_phi = !phi ? 0 : Math.sin(phi);
|
|
431
|
+
let c_phi = !phi ? 1 : Math.cos(phi);
|
|
397
432
|
|
|
398
433
|
let hd_x = (x1 - x2) / 2;
|
|
399
434
|
let hd_y = (y1 - y2) / 2;
|
|
@@ -408,8 +443,8 @@ export function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, swe
|
|
|
408
443
|
// Step 3: Ensure radii are large enough
|
|
409
444
|
let lambda = (x1_ * x1_) / (rx * rx) + (y1_ * y1_) / (ry * ry);
|
|
410
445
|
if (lambda > 1) {
|
|
411
|
-
rx = rx * sqrt(lambda);
|
|
412
|
-
ry = ry * sqrt(lambda);
|
|
446
|
+
rx = rx * Math.sqrt(lambda);
|
|
447
|
+
ry = ry * Math.sqrt(lambda);
|
|
413
448
|
|
|
414
449
|
// save real rx/ry
|
|
415
450
|
arcData.rx = rx;
|
|
@@ -424,7 +459,7 @@ export function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, swe
|
|
|
424
459
|
//console.log('error:', rx, ry, rxy1_, ryx1_);
|
|
425
460
|
throw Error("start point can not be same as end point");
|
|
426
461
|
}
|
|
427
|
-
let coe = sqrt(abs((rxry * rxry - sum_of_sq) / sum_of_sq));
|
|
462
|
+
let coe = Math.sqrt(Math.abs((rxry * rxry - sum_of_sq) / sum_of_sq));
|
|
428
463
|
if (largeArc == sweep) {
|
|
429
464
|
coe = -coe;
|
|
430
465
|
}
|
|
@@ -596,33 +631,92 @@ export function getTangentAngle(rx, ry, parametricAngle) {
|
|
|
596
631
|
return tangentAngle;
|
|
597
632
|
}
|
|
598
633
|
|
|
599
|
-
export function bezierhasExtreme(p0, cpts = [], angleThreshold = 0.05) {
|
|
600
|
-
let isCubic = cpts.length === 3 ? true : false;
|
|
601
|
-
let cp1 = cpts[0] || null
|
|
602
|
-
let cp2 = isCubic ? cpts[1] : null;
|
|
603
|
-
let p = isCubic ? cpts[2] : cpts[1];
|
|
604
|
-
let PIquarter = Math.PI * 0.5;
|
|
605
634
|
|
|
606
|
-
|
|
607
|
-
extCp2 = false;
|
|
635
|
+
export function bezierhasExtreme(p0 = null, cpts = []) {
|
|
608
636
|
|
|
609
|
-
|
|
610
|
-
|
|
637
|
+
if (!p0) {
|
|
638
|
+
p0 = cpts[0]
|
|
639
|
+
cpts = cpts.slice(1, cpts.length)
|
|
640
|
+
}
|
|
611
641
|
|
|
612
|
-
|
|
642
|
+
let l = cpts.length;
|
|
643
|
+
let p = cpts[l - 1];
|
|
644
|
+
let cp1 = cpts[0]
|
|
645
|
+
let cp2 = l === 3 ? cpts[1] : cp1
|
|
613
646
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
647
|
+
// get bounding box
|
|
648
|
+
/**
|
|
649
|
+
* if control points are within
|
|
650
|
+
* bounding box of start and end point
|
|
651
|
+
* we cant't have extremes
|
|
652
|
+
*/
|
|
653
|
+
let top = Math.min(p0.y, p.y)
|
|
654
|
+
let left = Math.min(p0.x, p.x)
|
|
655
|
+
let right = Math.max(p0.x, p.x)
|
|
656
|
+
let bottom = Math.max(p0.y, p.y)
|
|
657
|
+
|
|
658
|
+
// within bbox - can't have extremes
|
|
659
|
+
if (
|
|
660
|
+
cp1.y >= top && cp1.y <= bottom &&
|
|
661
|
+
cp2.y >= top && cp2.y <= bottom &&
|
|
662
|
+
cp1.x >= left && cp1.x <= right &&
|
|
663
|
+
cp2.x >= left && cp2.x <= right
|
|
664
|
+
) {
|
|
665
|
+
return false
|
|
618
666
|
}
|
|
619
|
-
|
|
667
|
+
|
|
668
|
+
return true
|
|
669
|
+
|
|
620
670
|
}
|
|
621
671
|
|
|
622
672
|
|
|
673
|
+
export function getSemiExtremesT(p0, cp1, cp2, p, angleRad = Math.PI / 2) {
|
|
674
|
+
// Rotate all points by -angleRad
|
|
675
|
+
let cos = Math.cos(-angleRad);
|
|
676
|
+
let sin = Math.sin(-angleRad);
|
|
677
|
+
|
|
678
|
+
const rotatePoint = (point) => ({
|
|
679
|
+
x: point.x * cos - point.y * sin,
|
|
680
|
+
y: point.x * sin + point.y * cos
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
let p0Rot = rotatePoint(p0);
|
|
684
|
+
let cp1Rot = rotatePoint(cp1);
|
|
685
|
+
let cp2Rot = rotatePoint(cp2);
|
|
686
|
+
let pRot = rotatePoint(p);
|
|
687
|
+
|
|
688
|
+
// Now find x-extrema in rotated coordinate system
|
|
689
|
+
// These correspond to points where original tangent angle = angleRad
|
|
623
690
|
|
|
624
|
-
|
|
625
|
-
let
|
|
691
|
+
let a = -3 * p0Rot.x + 9 * cp1Rot.x - 9 * cp2Rot.x + 3 * pRot.x;
|
|
692
|
+
let b = 6 * p0Rot.x - 12 * cp1Rot.x + 6 * cp2Rot.x;
|
|
693
|
+
let c = 3 * cp1Rot.x - 3 * p0Rot.x;
|
|
694
|
+
|
|
695
|
+
let extremes = [];
|
|
696
|
+
|
|
697
|
+
if (Math.abs(a) < 1e-12) {
|
|
698
|
+
if (Math.abs(b) > 1e-12) {
|
|
699
|
+
let t = -c / b;
|
|
700
|
+
if (t > 0 && t < 1) extremes.push(t);
|
|
701
|
+
}
|
|
702
|
+
return extremes;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
let discriminant = b * b - 4 * a * c;
|
|
706
|
+
if (discriminant >= 0) {
|
|
707
|
+
let sqrtDisc = Math.sqrt(discriminant);
|
|
708
|
+
let t1 = (-b + sqrtDisc) / (2 * a);
|
|
709
|
+
let t2 = (-b - sqrtDisc) / (2 * a);
|
|
710
|
+
|
|
711
|
+
if (t1 > 0 && t1 < 1) extremes.push(t1);
|
|
712
|
+
if (t2 > 0 && t2 < 1) extremes.push(t2);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
return extremes;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
export function getBezierExtremeT(pts, { addExtremes = true, addSemiExtremes = false } = {}) {
|
|
719
|
+
let tArr = pts.length === 4 ? cubicBezierExtremeT(pts[0], pts[1], pts[2], pts[3], { addExtremes, addSemiExtremes }) : quadraticBezierExtremeT(pts[0], pts[1], pts[2], { addExtremes, addSemiExtremes });
|
|
626
720
|
return tArr;
|
|
627
721
|
}
|
|
628
722
|
|
|
@@ -737,10 +831,179 @@ export function getArcExtemes(p0, values) {
|
|
|
737
831
|
|
|
738
832
|
|
|
739
833
|
|
|
834
|
+
export function getTatAngles(cpts = [], angles = []) {
|
|
835
|
+
|
|
836
|
+
let l = cpts.length;
|
|
837
|
+
let isCubic = l === 4;
|
|
838
|
+
//console.log(angles, isCubic);
|
|
839
|
+
|
|
840
|
+
if (!angles.length) angles = [0];
|
|
841
|
+
|
|
842
|
+
let anglesL = angles.length;
|
|
843
|
+
let tArr = []
|
|
844
|
+
|
|
845
|
+
for (let i = 0; i < anglesL; i++) {
|
|
846
|
+
|
|
847
|
+
let ang = angles[i];
|
|
848
|
+
let cptsN = ang ? [] : cpts.slice(0)
|
|
849
|
+
|
|
850
|
+
// rotate cpts
|
|
851
|
+
if (ang) {
|
|
852
|
+
for (let j = 0; j < l; j++) {
|
|
853
|
+
let pt = cpts[j]
|
|
854
|
+
cptsN.push(rotatePoint(pt, 0, 0, ang))
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// get t arr
|
|
859
|
+
let tVals = isCubic ? getTatCubicExtreme(...cptsN) : getTatQuadraticExtreme(...cptsN)
|
|
860
|
+
tArr.push(...tVals)
|
|
861
|
+
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// deduplicate and sort
|
|
865
|
+
tArr = [... new Set(tArr)].sort()
|
|
866
|
+
|
|
867
|
+
return tArr;
|
|
868
|
+
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
// t at cubic bezier extreme
|
|
874
|
+
export function getTatCubicExtreme(p0, cp1, cp2, p) {
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* if control points are within
|
|
878
|
+
* bounding box of start and end point
|
|
879
|
+
* we cant't have extremes
|
|
880
|
+
*/
|
|
881
|
+
if (!bezierhasExtreme(p0, [cp1, cp2, p])) {
|
|
882
|
+
//console.log('no extreme');
|
|
883
|
+
return []
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
let [x0, y0, x1, y1, x2, y2, x3, y3] = [p0.x, p0.y, cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y];
|
|
887
|
+
let tArr = [], a, b, c, t, t1, t2, b2ac, sqrt_b2ac;
|
|
888
|
+
let e = 1e-8
|
|
889
|
+
|
|
890
|
+
for (let i = 0; i < 2; ++i) {
|
|
891
|
+
|
|
892
|
+
if (i == 0) {
|
|
893
|
+
b = 6 * x0 - 12 * x1 + 6 * x2;
|
|
894
|
+
a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
|
|
895
|
+
c = 3 * x1 - 3 * x0;
|
|
896
|
+
} else {
|
|
897
|
+
b = 6 * y0 - 12 * y1 + 6 * y2;
|
|
898
|
+
a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
|
|
899
|
+
c = 3 * y1 - 3 * y0;
|
|
900
|
+
}
|
|
901
|
+
if (Math.abs(a) < e) {
|
|
902
|
+
if (Math.abs(b) < e) {
|
|
903
|
+
continue;
|
|
904
|
+
}
|
|
905
|
+
t = -c / b;
|
|
906
|
+
if (t > 0 && t < 1) {
|
|
907
|
+
tArr.push(t);
|
|
908
|
+
}
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
911
|
+
b2ac = b * b - 4 * c * a;
|
|
912
|
+
if (b2ac < 0) {
|
|
913
|
+
if (Math.abs(b2ac) < e) {
|
|
914
|
+
t = -b / (2 * a);
|
|
915
|
+
if (t > 0 && t < 1) {
|
|
916
|
+
tArr.push(t);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
continue;
|
|
920
|
+
}
|
|
921
|
+
sqrt_b2ac = Math.sqrt(b2ac);
|
|
922
|
+
t1 = (-b + sqrt_b2ac) / (2 * a);
|
|
923
|
+
if (t1 > 0 && t1 < 1) {
|
|
924
|
+
tArr.push(t1);
|
|
925
|
+
}
|
|
926
|
+
t2 = (-b - sqrt_b2ac) / (2 * a);
|
|
927
|
+
if (t2 > 0 && t2 < 1) {
|
|
928
|
+
tArr.push(t2);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
let j = tArr.length;
|
|
933
|
+
while (j--) {
|
|
934
|
+
t = tArr[j];
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
return [...new Set(tArr)].sort();
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
//For quadratic bezier.
|
|
944
|
+
export function getTatQuadraticExtreme(p0, cp1, p) {
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* if control points are within
|
|
948
|
+
* bounding box of start and end point
|
|
949
|
+
* we cant't have extremes
|
|
950
|
+
*/
|
|
951
|
+
if (!bezierhasExtreme(p0, [cp1, p])) {
|
|
952
|
+
//console.log('no extreme');
|
|
953
|
+
return []
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
let a, b, c, t;
|
|
957
|
+
let [x0, y0, x1, y1, x2, y2] = [p0.x, p0.y, cp1.x, cp1.y, p.x, p.y];
|
|
958
|
+
let tArr = [];
|
|
959
|
+
|
|
960
|
+
for (let i = 0; i < 2; ++i) {
|
|
961
|
+
a = i == 0 ? x0 - 2 * x1 + x2 : y0 - 2 * y1 + y2;
|
|
962
|
+
b = i == 0 ? -2 * x0 + 2 * x1 : -2 * y0 + 2 * y1;
|
|
963
|
+
c = i == 0 ? x0 : y0;
|
|
964
|
+
if (Math.abs(a) > 1e-12) {
|
|
965
|
+
t = -b / (2 * a);
|
|
966
|
+
if (t > 0 && t < 1) {
|
|
967
|
+
tArr.push(t);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
return [...new Set(tArr)].sort();
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
|
|
976
|
+
|
|
740
977
|
// cubic bezier.
|
|
741
|
-
export function cubicBezierExtremeT(p0, cp1, cp2, p
|
|
978
|
+
export function cubicBezierExtremeT(p0, cp1, cp2, p,
|
|
979
|
+
{ addExtremes = true, addSemiExtremes = false } = {}) {
|
|
980
|
+
|
|
981
|
+
|
|
982
|
+
// rotate cpts for semi extremes
|
|
983
|
+
const rotatePoint = (pt) => {
|
|
984
|
+
|
|
985
|
+
const angleRad = Math.PI / 4
|
|
986
|
+
const cos = Math.cos(angleRad);
|
|
987
|
+
const sin = Math.sin(angleRad);
|
|
988
|
+
|
|
989
|
+
return {
|
|
990
|
+
x: pt.x * cos - pt.y * sin,
|
|
991
|
+
y: pt.x * sin + pt.y * cos
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
|
|
995
|
+
|
|
996
|
+
if (addSemiExtremes) {
|
|
997
|
+
p0 = rotatePoint(p0)
|
|
998
|
+
cp1 = rotatePoint(cp1)
|
|
999
|
+
cp2 = rotatePoint(cp2)
|
|
1000
|
+
p = rotatePoint(p)
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
|
|
742
1004
|
let [x0, y0, x1, y1, x2, y2, x3, y3] = [p0.x, p0.y, cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y];
|
|
743
1005
|
|
|
1006
|
+
|
|
744
1007
|
/**
|
|
745
1008
|
* if control points are within
|
|
746
1009
|
* bounding box of start and end point
|
|
@@ -760,8 +1023,8 @@ export function cubicBezierExtremeT(p0, cp1, cp2, p) {
|
|
|
760
1023
|
return []
|
|
761
1024
|
}
|
|
762
1025
|
|
|
763
|
-
let tArr = [],
|
|
764
|
-
|
|
1026
|
+
let tArr = [], a, b, c, t, t1, t2, b2ac, sqrt_b2ac;
|
|
1027
|
+
|
|
765
1028
|
for (let i = 0; i < 2; ++i) {
|
|
766
1029
|
if (i == 0) {
|
|
767
1030
|
b = 6 * x0 - 12 * x1 + 6 * x2;
|
|
@@ -772,8 +1035,8 @@ export function cubicBezierExtremeT(p0, cp1, cp2, p) {
|
|
|
772
1035
|
a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
|
|
773
1036
|
c = 3 * y1 - 3 * y0;
|
|
774
1037
|
}
|
|
775
|
-
if (Math.abs(a) < 1e-
|
|
776
|
-
if (Math.abs(b) < 1e-
|
|
1038
|
+
if (Math.abs(a) < 1e-8) {
|
|
1039
|
+
if (Math.abs(b) < 1e-8) {
|
|
777
1040
|
continue;
|
|
778
1041
|
}
|
|
779
1042
|
t = -c / b;
|
|
@@ -784,7 +1047,7 @@ export function cubicBezierExtremeT(p0, cp1, cp2, p) {
|
|
|
784
1047
|
}
|
|
785
1048
|
b2ac = b * b - 4 * c * a;
|
|
786
1049
|
if (b2ac < 0) {
|
|
787
|
-
if (Math.abs(b2ac) < 1e-
|
|
1050
|
+
if (Math.abs(b2ac) < 1e-8) {
|
|
788
1051
|
t = -b / (2 * a);
|
|
789
1052
|
if (0 < t && t < 1) {
|
|
790
1053
|
tArr.push(t);
|
|
@@ -807,13 +1070,44 @@ export function cubicBezierExtremeT(p0, cp1, cp2, p) {
|
|
|
807
1070
|
while (j--) {
|
|
808
1071
|
t = tArr[j];
|
|
809
1072
|
}
|
|
1073
|
+
|
|
1074
|
+
//console.log('addSemiExtremes', addSemiExtremes, addExtremes, tArr);
|
|
1075
|
+
|
|
810
1076
|
return tArr;
|
|
811
1077
|
}
|
|
812
1078
|
|
|
1079
|
+
export function isMultipleOf45(angleRad, epsilon = 0.001) {
|
|
1080
|
+
let rad90 = Math.PI / 2;
|
|
1081
|
+
let rad45 = rad90 / 2;
|
|
1082
|
+
let isRightAngle = Math.abs(angleRad / rad90 - Math.round(angleRad / rad90)) < epsilon
|
|
1083
|
+
return !isRightAngle ? Math.abs(angleRad / rad45 - Math.round(angleRad / rad45)) < epsilon : false;
|
|
1084
|
+
}
|
|
813
1085
|
|
|
814
1086
|
|
|
815
1087
|
//For quadratic bezier.
|
|
816
|
-
export function quadraticBezierExtremeT(p0, cp1, p) {
|
|
1088
|
+
export function quadraticBezierExtremeT(p0, cp1, p, { addExtremes = true, addSemiExtremes = false } = {}) {
|
|
1089
|
+
|
|
1090
|
+
|
|
1091
|
+
// rotate cpts for semi extremes
|
|
1092
|
+
const rotatePoint = (pt) => {
|
|
1093
|
+
const angleRad = -Math.PI / 4
|
|
1094
|
+
const cos = Math.cos(angleRad);
|
|
1095
|
+
const sin = Math.sin(angleRad);
|
|
1096
|
+
|
|
1097
|
+
return {
|
|
1098
|
+
x: pt.x * cos - pt.y * sin,
|
|
1099
|
+
y: pt.x * sin + pt.y * cos
|
|
1100
|
+
}
|
|
1101
|
+
};
|
|
1102
|
+
|
|
1103
|
+
|
|
1104
|
+
if (addSemiExtremes) {
|
|
1105
|
+
p0 = rotatePoint(p0)
|
|
1106
|
+
cp1 = rotatePoint(cp1)
|
|
1107
|
+
p = rotatePoint(p)
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
|
|
817
1111
|
/**
|
|
818
1112
|
* if control points are within
|
|
819
1113
|
* bounding box of start and end point
|
|
@@ -930,24 +1224,56 @@ export function intersectLines(p1, p2, p3, p4) {
|
|
|
930
1224
|
|
|
931
1225
|
|
|
932
1226
|
/**
|
|
933
|
-
*
|
|
934
|
-
*
|
|
1227
|
+
* get distance between 2 points
|
|
1228
|
+
* pythagorean theorem
|
|
935
1229
|
*/
|
|
936
|
-
export function
|
|
1230
|
+
export function getDistance(p1, p2, isArray = false) {
|
|
1231
|
+
//if(Array.isArray(p1)) isArray = true;
|
|
1232
|
+
//console.log(p1, p2);
|
|
1233
|
+
let dx = isArray ? p2[0] - p1[0] : (p2.x - p1.x);
|
|
1234
|
+
let dy = isArray ? p2[1] - p1[1] : (p2.y - p1.y);
|
|
1235
|
+
|
|
1236
|
+
//console.log('dx', dx, dy, p1, p2);
|
|
1237
|
+
return sqrt(dx * dx + dy * dy);
|
|
1238
|
+
}
|
|
937
1239
|
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1240
|
+
// just an alias
|
|
1241
|
+
export function lineLength(p1, p2) {
|
|
1242
|
+
return getDistance(p1, p2)
|
|
1243
|
+
}
|
|
941
1244
|
|
|
942
|
-
/*
|
|
943
|
-
let diffX = pt2.x - pt1.x;
|
|
944
|
-
let diffY = pt2.y - pt1.y;
|
|
945
|
-
let diff = Math.abs(diffX + diffY) / 2;
|
|
946
|
-
*/
|
|
947
1245
|
|
|
948
|
-
|
|
1246
|
+
export function getSquareDistance(p1, p2) {
|
|
1247
|
+
let dx = (p2.x - p1.x);
|
|
1248
|
+
let dy = (p2.y - p1.y);
|
|
1249
|
+
return dx * dx + dy * dy
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
|
|
1253
|
+
|
|
1254
|
+
/**
|
|
1255
|
+
* get Manhattan/Cab distance
|
|
1256
|
+
* based on x/y deltas
|
|
1257
|
+
* sloppy but fast
|
|
1258
|
+
*/
|
|
1259
|
+
export function getDistManhattan(pt1, pt2) {
|
|
1260
|
+
let dx = Math.abs(pt2.x - pt1.x);
|
|
1261
|
+
let dy = Math.abs(pt2.y - pt1.y);
|
|
1262
|
+
return dx + dy;
|
|
949
1263
|
}
|
|
950
1264
|
|
|
1265
|
+
/**
|
|
1266
|
+
* sloppy distance calculation
|
|
1267
|
+
* based on "half Manhattan/Cab" distance
|
|
1268
|
+
*/
|
|
1269
|
+
|
|
1270
|
+
export function getDistAv(pt1, pt2) {
|
|
1271
|
+
let dx = Math.abs(pt2.x - pt1.x);
|
|
1272
|
+
let dy = Math.abs(pt2.y - pt1.y);
|
|
1273
|
+
return (dx + dy) * 0.5;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
|
|
951
1277
|
/**
|
|
952
1278
|
* get command dimensions
|
|
953
1279
|
* for threshold value
|
|
@@ -253,7 +253,8 @@ export function getBezierArea(pts, absolute=false) {
|
|
|
253
253
|
|
|
254
254
|
export function getPolygonArea(points, absolute=false) {
|
|
255
255
|
let area = 0;
|
|
256
|
-
|
|
256
|
+
let l = points.length;
|
|
257
|
+
for (let i = 0; l && i < l; i++) {
|
|
257
258
|
let addX = points[i].x;
|
|
258
259
|
let addY = points[i === points.length - 1 ? 0 : i + 1].y;
|
|
259
260
|
let subX = points[i === points.length - 1 ? 0 : i + 1].x;
|