svg-path-simplify 0.4.5 → 0.4.7
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/CHANGELOG.md +14 -0
- package/README.md +24 -14
- package/dist/svg-path-simplify.esm.js +1620 -1216
- package/dist/svg-path-simplify.esm.min.js +2 -2
- package/dist/svg-path-simplify.js +1620 -1215
- package/dist/svg-path-simplify.min.js +2 -2
- package/dist/svg-path-simplify.pathdata.esm.js +298 -665
- package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
- package/dist/svg-path-simplify.poly.cjs +8 -9
- package/drawing.svg +62 -0
- package/index.html +11 -5
- package/package.json +1 -1
- package/src/index.js +3 -1
- package/src/pathData_get_intersections.js +777 -0
- package/src/pathData_offset.js +201 -0
- package/src/pathData_simplify_cubic.js +14 -34
- package/src/pathData_simplify_cubic_extrapolate.js +147 -8
- package/src/pathData_simplify_cubicsToArcs.js +65 -21
- package/src/pathSimplify-main.js +259 -60
- package/src/pathSimplify-presets.js +1 -0
- package/src/poly-fit-curve-schneider.js +171 -132
- package/src/poly-fit-curve-schneider_check_bulge.js +131 -0
- package/src/poly-offset.js +133 -0
- package/src/simplify_poly_RC.js +29 -7
- package/src/svgii/geometry.js +130 -27
- package/src/svgii/geometry_bbox.js +1 -1
- package/src/svgii/pathData_analyze.js +6 -28
- package/src/svgii/pathData_convert.js +70 -15
- package/src/svgii/pathData_remove_collinear.js +32 -36
- package/src/svgii/pathData_simplify_refineExtremes.js +1 -3
- package/src/svgii/pathData_stringify.js +132 -6
- package/src/svgii/pathData_toPolygon.js +33 -23
- package/src/svgii/poly_analyze.js +272 -125
- package/src/svgii/poly_analyze_cleanup.js +311 -0
- package/src/svgii/poly_analyze_getTangents.js +125 -0
- package/src/svgii/poly_analyze_get_chunks.js +3 -1
- package/src/svgii/poly_normalize.js +12 -0
- package/src/svgii/poly_to_pathdata.js +477 -8
- package/src/svgii/rounding.js +29 -39
- package/src/svgii/svg_cleanup.js +42 -54
- package/src/svgii/svg_cleanup_normalize_transforms.js +1 -1
- package/src/svgii/visualize.js +33 -2
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { harmonizeCubicCpts, harmonizeCubicCptsThird } from "./pathData_simplify_harmonize_cpts";
|
|
12
|
+
import { adjustTangentAngle, areaDeviationTooLarge } from "./poly-fit-curve-schneider_check_bulge";
|
|
12
13
|
import { checkLineIntersection, getAngle, getDistance, getSquareDistance, pointAtT, reducePoints, rotatePoint } from "./svgii/geometry";
|
|
13
14
|
import { getPolygonArea } from "./svgii/geometry_area";
|
|
14
15
|
import { detectRegularPolygon, getPolyCentroid } from "./svgii/poly_analyze";
|
|
@@ -30,6 +31,8 @@ export function fitCurveSchneider(pts, {
|
|
|
30
31
|
keepCorners = true
|
|
31
32
|
} = {}) {
|
|
32
33
|
|
|
34
|
+
//console.log('fitCurveSchneider');
|
|
35
|
+
|
|
33
36
|
if (pts.length === 1) {
|
|
34
37
|
return [];
|
|
35
38
|
}
|
|
@@ -47,112 +50,215 @@ export function fitCurveSchneider(pts, {
|
|
|
47
50
|
//keepCorners = false
|
|
48
51
|
|
|
49
52
|
let len = pts.length;
|
|
53
|
+
let cp1, cp2;
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
//adjust 1st and last angle
|
|
57
|
+
let p0 = { x: pts[0].x, y: pts[0].y }
|
|
58
|
+
let p1 = { x: pts[1].x, y: pts[1].y }
|
|
59
|
+
cp1 = p1
|
|
60
|
+
|
|
61
|
+
let p2 = pts[2] ? { x: pts[2].x, y: pts[2].y } : null
|
|
62
|
+
|
|
63
|
+
let pL = { x: pts[len - 1].x, y: pts[len - 1].y }
|
|
64
|
+
let pL1 = { x: pts[len - 2].x, y: pts[len - 2].y }
|
|
65
|
+
cp2 = pL1
|
|
66
|
+
let pL2 = pts[len - 3] ? { x: pts[len - 3].x, y: pts[len - 3].y } : null
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
// adjust start angles
|
|
70
|
+
if (p2) {
|
|
71
|
+
cp1 = adjustTangentAngle(cp1, p0, p1, p2)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (pL2) {
|
|
75
|
+
cp2 = adjustTangentAngle(cp2, pL, pL1, pL2)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
let leftTangent = createTangent(cp1, pts[0]);
|
|
80
|
+
let rightTangent = createTangent(cp2, pts[len - 1]);
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
/*
|
|
50
84
|
let leftTangent = createTangent(pts[1], pts[0]);
|
|
51
85
|
let rightTangent = createTangent(pts[len - 2], pts[len - 1]);
|
|
52
|
-
|
|
86
|
+
*/
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
//let beziers = fitCubic(pts, leftTangent, rightTangent, maxError, keepCorners);
|
|
90
|
+
let beziers = fitCubicIterative(pts, leftTangent, rightTangent, maxError, keepCorners);
|
|
91
|
+
//console.log('beziers', JSON.parse(JSON.stringify(beziers)));
|
|
92
|
+
|
|
53
93
|
|
|
54
94
|
// create pathdata
|
|
55
95
|
let pathData = bezierPtsToPathData(beziers)
|
|
56
|
-
let cp1, cp2;
|
|
57
96
|
|
|
58
97
|
adjustCpts = false
|
|
98
|
+
// adjustCpts = true;
|
|
59
99
|
//harmonize = false;
|
|
60
|
-
|
|
61
|
-
adjustCpts = true;
|
|
62
100
|
//harmonize = true;
|
|
63
101
|
|
|
64
102
|
|
|
65
|
-
if (adjustCpts) {
|
|
66
103
|
|
|
67
|
-
|
|
104
|
+
//console.log('pathData schneider', pathData);
|
|
105
|
+
return pathData
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Fit a Bezier curve to a (sub)set of digitized pts.
|
|
111
|
+
* Your code should not call this function directly. Use fitCurve instead.
|
|
112
|
+
* control-point-1, control-point-2, second-point] and pts are [x, y]
|
|
113
|
+
*/
|
|
114
|
+
|
|
68
115
|
|
|
69
|
-
|
|
70
|
-
let com1 = pathData[0]
|
|
116
|
+
function fitCubicIterative(pts = [], leftTangent = { x: 0, y: 0 }, rightTangent = { x: 0, y: 0 }, error = 1) {
|
|
71
117
|
|
|
72
|
-
|
|
73
|
-
|
|
118
|
+
//error = 0.01
|
|
119
|
+
//console.log('error', error);
|
|
120
|
+
//console.log('fitCubicIterative');
|
|
74
121
|
|
|
75
|
-
|
|
76
|
-
let p0 = { x: pts[0].x, y: pts[0].y }
|
|
77
|
-
let p1 = { x: pts[1].x, y: pts[1].y }
|
|
78
|
-
let p2 = pts[2] ? { x: pts[2].x, y: pts[2].y } : null
|
|
122
|
+
let beziers = [];
|
|
79
123
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
124
|
+
// stack holds segments to process
|
|
125
|
+
let stack = [{
|
|
126
|
+
pts,
|
|
127
|
+
leftTangent,
|
|
128
|
+
rightTangent
|
|
129
|
+
}];
|
|
86
130
|
|
|
87
|
-
|
|
88
|
-
let pL1 = { x: pts[len - 2].x, y: pts[len - 2].y }
|
|
89
|
-
let pL2 = pts[len - 3] ? { x: pts[len - 3][0], y: pts[len - 3][1] } : null
|
|
131
|
+
while (stack.length > 0) {
|
|
90
132
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
133
|
+
let current = stack.pop();
|
|
134
|
+
|
|
135
|
+
let pts = current.pts;
|
|
136
|
+
let leftTangent = current.leftTangent;
|
|
137
|
+
let rightTangent = current.rightTangent;
|
|
138
|
+
|
|
139
|
+
let MaxIterations = 20;
|
|
140
|
+
let bezCurve = [];
|
|
141
|
+
|
|
142
|
+
// === same logic as before ===
|
|
97
143
|
|
|
144
|
+
let u = chordLengthParameterize(pts);
|
|
145
|
+
let _generateAndReport = generateAndReport(pts, u, u, leftTangent, rightTangent);
|
|
146
|
+
|
|
147
|
+
bezCurve = _generateAndReport[0];
|
|
148
|
+
let maxError = _generateAndReport[1];
|
|
149
|
+
let splitPoint = _generateAndReport[2];
|
|
150
|
+
|
|
151
|
+
// BULGE CHECK
|
|
98
152
|
/*
|
|
99
|
-
|
|
100
|
-
let
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
153
|
+
let checkBulge = areaDeviationTooLarge(pts, bezCurve);
|
|
154
|
+
let { isBulged, bezierNew, ptsN } = checkBulge;
|
|
155
|
+
isBulged = false
|
|
156
|
+
|
|
157
|
+
if (isBulged) {
|
|
158
|
+
beziers.push(bezierNew);
|
|
159
|
+
// skip this segment entirely (acts like your "early exit")
|
|
160
|
+
continue;
|
|
106
161
|
}
|
|
107
162
|
*/
|
|
108
163
|
|
|
164
|
+
// ACCEPT
|
|
165
|
+
if (maxError === 0 || maxError < error) {
|
|
166
|
+
beziers.push(bezCurve);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ITERATIVE REFINEMENT
|
|
171
|
+
if (maxError < error * error) {
|
|
172
|
+
|
|
173
|
+
let uPrime = u;
|
|
174
|
+
let prevErr = maxError;
|
|
175
|
+
let prevSplit = splitPoint;
|
|
176
|
+
|
|
177
|
+
for (let i = 0; i < MaxIterations; i++) {
|
|
178
|
+
|
|
179
|
+
uPrime = reparameterize(bezCurve, pts, uPrime);
|
|
180
|
+
|
|
181
|
+
let _generateAndReport2 = generateAndReport(pts, u, uPrime, leftTangent, rightTangent);
|
|
182
|
+
|
|
183
|
+
bezCurve = _generateAndReport2[0];
|
|
184
|
+
maxError = _generateAndReport2[1];
|
|
185
|
+
splitPoint = _generateAndReport2[2];
|
|
186
|
+
|
|
187
|
+
if (maxError < error) {
|
|
188
|
+
beziers.push(bezCurve);
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
else if (splitPoint === prevSplit) {
|
|
193
|
+
let errChange = maxError / prevErr;
|
|
194
|
+
if (errChange > .9999 && errChange < 1.0001) {
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
prevErr = maxError;
|
|
200
|
+
prevSplit = splitPoint;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (maxError < error) continue;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// SPLIT (instead of recursion → push onto stack)
|
|
207
|
+
|
|
208
|
+
let centerVector = subtract(pts[splitPoint - 1], pts[splitPoint + 1]);
|
|
209
|
+
|
|
210
|
+
if (centerVector.x === 0 && centerVector.y === 0) {
|
|
211
|
+
centerVector = subtract(pts[splitPoint - 1], pts[splitPoint]);
|
|
212
|
+
centerVector = { x: -centerVector.y, y: centerVector.x };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let toCenterTangent = normalize(centerVector);
|
|
216
|
+
let fromCenterTangent = mulItems(toCenterTangent, -1);
|
|
217
|
+
|
|
218
|
+
// push RIGHT first, so LEFT is processed first (stack = LIFO)
|
|
219
|
+
stack.push({
|
|
220
|
+
pts: pts.slice(splitPoint),
|
|
221
|
+
leftTangent: fromCenterTangent,
|
|
222
|
+
rightTangent: rightTangent
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
stack.push({
|
|
226
|
+
pts: pts.slice(0, splitPoint + 1),
|
|
227
|
+
leftTangent: leftTangent,
|
|
228
|
+
rightTangent: toCenterTangent
|
|
229
|
+
});
|
|
109
230
|
}
|
|
110
231
|
|
|
111
|
-
//console.log(
|
|
112
|
-
return
|
|
232
|
+
//console.log(beziers);
|
|
233
|
+
return beziers;
|
|
113
234
|
}
|
|
114
235
|
|
|
115
236
|
|
|
116
|
-
/**
|
|
117
|
-
* Fit a Bezier curve to a (sub)set of digitized pts.
|
|
118
|
-
* Your code should not call this function directly. Use fitCurve instead.
|
|
119
|
-
*control-point-1, control-point-2, second-point] and pts are [x, y]
|
|
120
|
-
*/
|
|
121
|
-
function fitCubic(pts, leftTangent, rightTangent, error, keepCorners = false) {
|
|
122
237
|
|
|
238
|
+
function fitCubic(pts = [], leftTangent = { x: 0, y: 0 }, rightTangent = { x: 0, y: 0 }, error = 1, keepCorners = false) {
|
|
123
239
|
|
|
124
240
|
//Max times to try iterating (to find an acceptable curve)
|
|
125
241
|
let MaxIterations = 20;
|
|
126
|
-
let bezCurve;
|
|
127
|
-
|
|
128
|
-
/*
|
|
129
|
-
//Use heuristic if region only has two pts in it
|
|
130
|
-
if (pts.length === 2) {
|
|
131
|
-
let dist = getDistance(pts[0], pts[1], false) * 0.333;
|
|
132
|
-
bezCurve = [pts[0], addArrays(pts[0], mulItems(leftTangent, dist)), addArrays(pts[1], mulItems(rightTangent, dist)), pts[1]];
|
|
133
|
-
return [bezCurve];
|
|
134
|
-
}
|
|
135
|
-
*/
|
|
242
|
+
let bezCurve = [];
|
|
243
|
+
let beziers = [];
|
|
136
244
|
|
|
137
245
|
|
|
138
246
|
//Parameterize pts, and attempt to fit curve
|
|
139
247
|
let u = chordLengthParameterize(pts);
|
|
140
248
|
let _generateAndReport = generateAndReport(pts, u, u, leftTangent, rightTangent);
|
|
249
|
+
//console.log(leftTangent, u, error);
|
|
141
250
|
|
|
142
251
|
bezCurve = _generateAndReport[0];
|
|
143
|
-
|
|
144
252
|
let maxError = _generateAndReport[1];
|
|
145
253
|
let splitPoint = _generateAndReport[2];
|
|
146
254
|
|
|
147
255
|
|
|
148
|
-
// check if curve is bulged
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
bezCurve = bezierNew
|
|
155
|
-
}
|
|
256
|
+
// check if curve is bulged - quit
|
|
257
|
+
let checkBulge = areaDeviationTooLarge(pts, bezCurve);
|
|
258
|
+
let { isBulged, bezierNew } = checkBulge;
|
|
259
|
+
if (isBulged) {
|
|
260
|
+
beziers.push(bezierNew)
|
|
261
|
+
return beziers
|
|
156
262
|
}
|
|
157
263
|
|
|
158
264
|
|
|
@@ -197,7 +303,6 @@ function fitCubic(pts, leftTangent, rightTangent, error, keepCorners = false) {
|
|
|
197
303
|
}
|
|
198
304
|
|
|
199
305
|
//Fitting failed -- split at max error point and fit recursively
|
|
200
|
-
let beziers = [];
|
|
201
306
|
let centerVector = subtract(pts[splitPoint - 1], pts[splitPoint + 1]);
|
|
202
307
|
|
|
203
308
|
if (centerVector.x === 0 && centerVector.y === 0) {
|
|
@@ -211,9 +316,11 @@ function fitCubic(pts, leftTangent, rightTangent, error, keepCorners = false) {
|
|
|
211
316
|
//To and from need to point in opposite directions:
|
|
212
317
|
let fromCenterTangent = mulItems(toCenterTangent, -1);
|
|
213
318
|
|
|
319
|
+
/*
|
|
214
320
|
if (pts.length === 3) {
|
|
215
321
|
//splitPoint--
|
|
216
322
|
}
|
|
323
|
+
*/
|
|
217
324
|
|
|
218
325
|
beziers.push(
|
|
219
326
|
...fitCubic(pts.slice(0, splitPoint + 1), leftTangent, toCenterTangent, error, keepCorners),
|
|
@@ -487,66 +594,6 @@ function find_t(param, t_distMap, B_parts) {
|
|
|
487
594
|
}
|
|
488
595
|
|
|
489
596
|
|
|
490
|
-
function areaDeviationTooLarge(pts, bezCurve) {
|
|
491
|
-
|
|
492
|
-
let split = 4;
|
|
493
|
-
let step = 1 / split;
|
|
494
|
-
let l = pts.length
|
|
495
|
-
|
|
496
|
-
// create polygon from curve candidate
|
|
497
|
-
let poly = [bezCurve[0]];
|
|
498
|
-
let pt;
|
|
499
|
-
|
|
500
|
-
for (let i = 1; i < split; i++) {
|
|
501
|
-
let t = step * i
|
|
502
|
-
pt = pointAtT(bezCurve, t)
|
|
503
|
-
poly.push(pt);
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
poly.push(bezCurve[bezCurve.length - 1]);
|
|
507
|
-
|
|
508
|
-
// Original area
|
|
509
|
-
let polyArea = getPolygonArea(pts, false)
|
|
510
|
-
|
|
511
|
-
// flat line
|
|
512
|
-
if (!polyArea && pts.length === 2) {
|
|
513
|
-
polyArea = getSquareDistance(pts[0], pts[1]) * 0.01
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
let curveArea = getPolygonArea(poly, false);
|
|
517
|
-
let rat = curveArea / polyArea;
|
|
518
|
-
|
|
519
|
-
let isBulged = rat > 1.1
|
|
520
|
-
//|| rat < 0.9
|
|
521
|
-
// isBulged = rat > 2
|
|
522
|
-
let bezierNew = bezCurve
|
|
523
|
-
if (isBulged) {
|
|
524
|
-
let ptMid = pts[Math.floor(l * 0.5)]
|
|
525
|
-
let p = pts[l - 1];
|
|
526
|
-
/*
|
|
527
|
-
let cp1 = pointAtT([pts[0], ptMid], 0.666);
|
|
528
|
-
let cp2 = pointAtT([p, ptMid], 0.666);
|
|
529
|
-
//let ptMid2 = pts[Math.floor(pts.length*0.666)]
|
|
530
|
-
bezierNew = [pts[0], cp1, cp2, p]
|
|
531
|
-
//bezierNew = [pts[0], ptMid, ptMid, p]
|
|
532
|
-
|
|
533
|
-
//renderPoint(markers, cp1, 'cyan')
|
|
534
|
-
//renderPoint(markers, cp2, 'cyan')
|
|
535
|
-
//console.log('bezCurve', bezCurve, pts);
|
|
536
|
-
|
|
537
|
-
cp1 = pointAtT([bezCurve[0], bezCurve[3]], 0.333);
|
|
538
|
-
cp2 = pointAtT([bezCurve[0], bezCurve[3]], 0.666);
|
|
539
|
-
bezCurve = [bezCurve[0], cp1, cp2, bezCurve[3]]
|
|
540
|
-
*/
|
|
541
|
-
|
|
542
|
-
//console.log('!!!bulged');
|
|
543
|
-
|
|
544
|
-
bezCurve = [pts[0], ptMid, ptMid, p]
|
|
545
|
-
bezierNew = bezCurve
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
return { isBulged, bezierNew };
|
|
549
|
-
}
|
|
550
597
|
|
|
551
598
|
|
|
552
599
|
/**
|
|
@@ -630,14 +677,6 @@ function bezierQprime(bez, u, second = false) {
|
|
|
630
677
|
}
|
|
631
678
|
|
|
632
679
|
|
|
633
|
-
function adjustTangentAngle(cp, p0, p1, p2) {
|
|
634
|
-
let ang1 = getAngle(p0, p1)
|
|
635
|
-
let ang2 = getAngle(p0, p2)
|
|
636
|
-
let angDiff = (ang2 - ang1)
|
|
637
|
-
cp = rotatePoint(cp, p0.x, p0.y, -angDiff)
|
|
638
|
-
return cp
|
|
639
|
-
}
|
|
640
|
-
|
|
641
680
|
|
|
642
681
|
|
|
643
682
|
// convert to pathdata
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { getAngle, getBezierExtremeT, pointAtT, rotatePoint } from "./svgii/geometry";
|
|
2
|
+
import { getPolygonArea } from "./svgii/geometry_area";
|
|
3
|
+
import { pathDataToD } from "./svgii/pathData_stringify";
|
|
4
|
+
import { renderPath, renderPoint, renderPoly } from "./svgii/visualize";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export function checkExtremesInCurve(pts = [], bezCurve) {
|
|
8
|
+
|
|
9
|
+
let split = 4;
|
|
10
|
+
let l = pts.length
|
|
11
|
+
|
|
12
|
+
// create polygon from curve candidate
|
|
13
|
+
let pt;
|
|
14
|
+
let ptsN = pts;
|
|
15
|
+
let bezierNew = bezCurve
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* check extremes
|
|
19
|
+
*/
|
|
20
|
+
let [p0, cp1, cp2, p] = bezCurve;
|
|
21
|
+
let tEx = getBezierExtremeT([p0, cp1, cp2, p])
|
|
22
|
+
if (tEx.length) {
|
|
23
|
+
//console.log('bezCurve', 'isBulged', isBulged, bezCurve, 'tEx', tEx);
|
|
24
|
+
|
|
25
|
+
tEx = tEx.filter(t => t !== 0 && t !== 1)[0]
|
|
26
|
+
if (tEx) {
|
|
27
|
+
|
|
28
|
+
let pEx = pointAtT(bezCurve, tEx, false, true)
|
|
29
|
+
let { x, y } = pEx
|
|
30
|
+
//console.log('pEx', pEx, pts);
|
|
31
|
+
|
|
32
|
+
let seg1_p0 = p0;
|
|
33
|
+
let seg1_cpt1 = pEx.cpts[0];
|
|
34
|
+
let seg1_cpt2 = pEx.cpts[2];
|
|
35
|
+
let seg1_p = { x, y };
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
let seg2_p0 = p0;
|
|
39
|
+
let seg2_cpt1 = pEx.cpts[3];
|
|
40
|
+
let seg2_cpt2 = pEx.cpts[1];
|
|
41
|
+
let seg2_p = p;
|
|
42
|
+
|
|
43
|
+
let pathD = [
|
|
44
|
+
{ type: 'M', values: [seg1_p0.x, seg1_p0.y] },
|
|
45
|
+
{ type: 'C', values: [seg1_cpt1.x, seg1_cpt1.y, seg1_cpt2.x, seg1_cpt2.y, seg1_p.x, seg1_p.y] },
|
|
46
|
+
{ type: 'C', values: [seg2_cpt1.x, seg2_cpt1.y, seg2_cpt2.x, seg2_cpt2.y, seg2_p.x, seg2_p.y] },
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
bezierNew = [seg1_p0, seg1_cpt1, seg1_cpt2, seg1_p];
|
|
52
|
+
//isBulged = true
|
|
53
|
+
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
return { isBulged, bezierNew, ptsN };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
export function areaDeviationTooLarge(pts = [], bezCurve) {
|
|
64
|
+
|
|
65
|
+
let split = 4;
|
|
66
|
+
let step = 1 / split;
|
|
67
|
+
let l = pts.length
|
|
68
|
+
|
|
69
|
+
// create polygon from curve candidate
|
|
70
|
+
let poly = [bezCurve[0]];
|
|
71
|
+
let pt;
|
|
72
|
+
let ptsN = pts;
|
|
73
|
+
|
|
74
|
+
for (let i = 1; i < split; i++) {
|
|
75
|
+
let t = step * i
|
|
76
|
+
pt = pointAtT(bezCurve, t)
|
|
77
|
+
//renderPoint(markers, pt, 'red')
|
|
78
|
+
poly.push(pt);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
poly.push(bezCurve[bezCurve.length - 1]);
|
|
82
|
+
|
|
83
|
+
// Original area
|
|
84
|
+
let polyArea = getPolygonArea(pts, true)
|
|
85
|
+
|
|
86
|
+
// flat line
|
|
87
|
+
|
|
88
|
+
if (!polyArea && pts.length === 2) {
|
|
89
|
+
polyArea = 0.0000001
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
//renderPoint(markers, pts[0], 'green')
|
|
93
|
+
//renderPoint(markers, pts[1], 'cyan')
|
|
94
|
+
|
|
95
|
+
let curveArea = getPolygonArea(poly, true);
|
|
96
|
+
//let rat = curveArea / polyArea;
|
|
97
|
+
let areaDiff = Math.abs(polyArea - curveArea)
|
|
98
|
+
|
|
99
|
+
let rat = areaDiff / polyArea;
|
|
100
|
+
//console.log('rat', rat, curveArea, polyArea);
|
|
101
|
+
|
|
102
|
+
let isBulged = rat > 1
|
|
103
|
+
let bezierNew = bezCurve
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
if (isBulged) {
|
|
108
|
+
//renderPoint(markers, pts[0], 'green')
|
|
109
|
+
renderPoint(markers, pts[1], 'magenta')
|
|
110
|
+
bezierNew = [pts[0], pts[0], pts[1], pts[1]]
|
|
111
|
+
//return { isBulged, bezierNew };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return { isBulged, bezierNew, ptsN };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
export function adjustTangentAngle(cp, p0, p1, p2) {
|
|
120
|
+
let ang1 = getAngle(p0, p1)
|
|
121
|
+
let ang2 = getAngle(p0, p2)
|
|
122
|
+
let angDiff = (ang2 - ang1)
|
|
123
|
+
//let ang3 =
|
|
124
|
+
let f = 0.666
|
|
125
|
+
f = 1
|
|
126
|
+
//f=0
|
|
127
|
+
//f=0.75
|
|
128
|
+
//console.log(angDiff);
|
|
129
|
+
cp = rotatePoint(cp, p0.x, p0.y, -angDiff * f)
|
|
130
|
+
return cp
|
|
131
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { checkLineIntersection } from "./svgii/geometry";
|
|
2
|
+
|
|
3
|
+
export function getParallelPoly(pts=[], strokeWidth = 1, sweep = 1, closed=false) {
|
|
4
|
+
let l = pts.length;
|
|
5
|
+
|
|
6
|
+
let polyTop_lines = [];
|
|
7
|
+
let polyBottom_lines = [];
|
|
8
|
+
let polyTop = [];
|
|
9
|
+
let polyBottom = [];
|
|
10
|
+
|
|
11
|
+
for (let i = 1; i < l; i++) {
|
|
12
|
+
let p1 = pts[i - 1];
|
|
13
|
+
let p2 = pts[i];
|
|
14
|
+
let parralels = getParallels(p1, p2, strokeWidth);
|
|
15
|
+
let { top, bottom } = parralels;
|
|
16
|
+
polyTop_lines.push(top);
|
|
17
|
+
polyBottom_lines.push(bottom);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// find line intersections
|
|
21
|
+
l = polyTop_lines.length;
|
|
22
|
+
|
|
23
|
+
// add 1st points
|
|
24
|
+
polyTop.push(polyTop_lines[0][0]);
|
|
25
|
+
polyBottom.push(polyBottom_lines[0][0]);
|
|
26
|
+
|
|
27
|
+
for (let i = 1; i < l; i++) {
|
|
28
|
+
let l1_t = polyTop_lines[i - 1];
|
|
29
|
+
let l2_t = polyTop_lines[i];
|
|
30
|
+
let ptI_t = checkLineIntersection(
|
|
31
|
+
l1_t[0],
|
|
32
|
+
l1_t[1],
|
|
33
|
+
l2_t[1],
|
|
34
|
+
l2_t[0],
|
|
35
|
+
false,
|
|
36
|
+
true
|
|
37
|
+
);
|
|
38
|
+
let ptN_t = ptI_t ? ptI_t : l1_t[1];
|
|
39
|
+
|
|
40
|
+
let l1_b = polyBottom_lines[i - 1];
|
|
41
|
+
let l2_b = polyBottom_lines[i];
|
|
42
|
+
let ptI_b = checkLineIntersection(
|
|
43
|
+
l1_b[0],
|
|
44
|
+
l1_b[1],
|
|
45
|
+
l2_b[1],
|
|
46
|
+
l2_b[0],
|
|
47
|
+
false,
|
|
48
|
+
true
|
|
49
|
+
);
|
|
50
|
+
let ptN_b = ptI_b ? ptI_b : l1_t[1];
|
|
51
|
+
|
|
52
|
+
polyTop.push(ptN_t);
|
|
53
|
+
polyBottom.push(ptN_b);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// add last pts
|
|
57
|
+
polyTop.push(polyTop_lines[l - 1][1]);
|
|
58
|
+
polyBottom.push(polyBottom_lines[l - 1][1]);
|
|
59
|
+
|
|
60
|
+
if(closed){
|
|
61
|
+
let l=polyTop.length;
|
|
62
|
+
let ptI_1 =checkLineIntersection(polyTop[1],polyTop[0], polyTop[l-2],polyTop[l-1], false, true )
|
|
63
|
+
if(ptI_1){
|
|
64
|
+
//renderPoint(svg, ptI_1)
|
|
65
|
+
polyTop[0] = ptI_1
|
|
66
|
+
polyTop[l-1] = ptI_1
|
|
67
|
+
}
|
|
68
|
+
let ptI_2 =checkLineIntersection(polyBottom[1],polyBottom[0], polyBottom[l-2],polyBottom[l-1], false, true )
|
|
69
|
+
|
|
70
|
+
if(ptI_2){
|
|
71
|
+
//renderPoint(svg, ptI_2, 'green')
|
|
72
|
+
polyBottom[0] = ptI_2
|
|
73
|
+
polyBottom[l-1] = ptI_2
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return sweep
|
|
81
|
+
? { polyTop: polyBottom, polyBottom: polyTop }
|
|
82
|
+
: { polyTop, polyBottom };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function getParallels(p1, p2, strokeWidth = 1) {
|
|
86
|
+
let distance = strokeWidth * 0.5;
|
|
87
|
+
// Get direction vector
|
|
88
|
+
let dx = p2.x - p1.x;
|
|
89
|
+
let dy = p2.y - p1.y;
|
|
90
|
+
|
|
91
|
+
// Calculate normalized perpendicular vector (rotated 90°)
|
|
92
|
+
let length = Math.hypot(dx, dy);
|
|
93
|
+
let perpX = -dy / length; // Left/up perpendicular
|
|
94
|
+
let perpY = dx / length;
|
|
95
|
+
|
|
96
|
+
// Scale by distance once
|
|
97
|
+
let offsetX = perpX * distance;
|
|
98
|
+
let offsetY = perpY * distance;
|
|
99
|
+
|
|
100
|
+
// Return both parallels in one go
|
|
101
|
+
|
|
102
|
+
let bottom = [
|
|
103
|
+
{ x: p1.x - offsetX, y: p1.y - offsetY },
|
|
104
|
+
{ x: p2.x - offsetX, y: p2.y - offsetY }
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
let top = [
|
|
108
|
+
{ x: p1.x + offsetX, y: p1.y + offsetY },
|
|
109
|
+
{ x: p2.x + offsetX, y: p2.y + offsetY }
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
//renderPoint(svg, top[0])
|
|
113
|
+
//renderPoint(svg, bottom[0], 'blue')
|
|
114
|
+
|
|
115
|
+
return { top, bottom };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function getExtendedPoint(p1, p2, distance) {
|
|
119
|
+
// Get direction vector
|
|
120
|
+
let dx = p2.x - p1.x;
|
|
121
|
+
let dy = p2.y - p1.y;
|
|
122
|
+
let length = Math.hypot(dx, dy);
|
|
123
|
+
|
|
124
|
+
// Normalize direction
|
|
125
|
+
let ndx = dx / length;
|
|
126
|
+
let ndy = dy / length;
|
|
127
|
+
|
|
128
|
+
// Extend along the line direction
|
|
129
|
+
return {
|
|
130
|
+
x: p2.x + ndx * distance,
|
|
131
|
+
y: p2.y + ndy * distance
|
|
132
|
+
};
|
|
133
|
+
}
|