svg-path-simplify 0.3.0 → 0.3.4
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 +3 -1
- package/dist/svg-path-simplify.esm.js +2786 -2764
- package/dist/svg-path-simplify.esm.min.js +5 -7
- package/dist/svg-path-simplify.js +2786 -2764
- package/dist/svg-path-simplify.min.js +5 -7
- package/dist/svg-path-simplify.pathdata.esm.js +31 -2
- package/dist/svg-path-simplify.pathdata.esm.min.js +5 -5
- package/index.html +209 -158
- package/package.json +1 -1
- package/src/detect_input.js +30 -3
- package/src/pathSimplify-main.js +131 -66
- package/src/poly-fit-curve-schneider.js +266 -175
- package/src/simplify_poly_RDP.js +101 -1
- package/src/simplify_poly_radial_distance.js +85 -2
- package/src/svgii/geometry.js +1 -0
- package/src/svgii/pathData_fromPoly.js +22 -7
- package/src/svgii/pathData_toPolygon.js +122 -230
- package/src/svgii/poly_analyze.js +114 -435
- package/src/svgii/poly_analyze_get_chunks.js +67 -0
- package/src/svgii/poly_normalize.js +51 -0
- package/src/svgii/poly_to_pathdata.js +51 -24
- package/src/svgii/rounding.js +36 -0
- package/src/svgii/svg_cleanup.js +10 -0
|
@@ -9,90 +9,59 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { harmonizeCubicCpts, harmonizeCubicCptsThird } from "./pathData_simplify_harmonize_cpts";
|
|
12
|
-
import { getAngle, getDistance, pointAtT, rotatePoint } from "./svgii/geometry";
|
|
13
|
-
import {
|
|
12
|
+
import { getAngle, getDistance, getSquareDistance, pointAtT, rotatePoint } from "./svgii/geometry";
|
|
13
|
+
import { getPolygonArea } from "./svgii/geometry_area";
|
|
14
|
+
import { renderPath, renderPoint } from "./svgii/visualize";
|
|
14
15
|
|
|
15
16
|
|
|
16
|
-
let polyPtsToArray = (pts) => {
|
|
17
|
-
return Array.from(pts).map(pt => [pt.x, pt.y])
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// convert to pathdata
|
|
21
|
-
let bezierPtsToPathData = (beziers) => {
|
|
22
|
-
//let pathData = [{ type: 'M', values: [beziers[0][0][0], beziers[0][0][1]] }];
|
|
23
|
-
let pathData = [];
|
|
24
|
-
|
|
25
|
-
beziers.forEach(bez => {
|
|
26
|
-
let cp1 = bez[1]
|
|
27
|
-
let cp2 = bez[2]
|
|
28
|
-
let p = bez[3]
|
|
29
|
-
let com = { type: 'C', values: [cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1]] }
|
|
30
|
-
pathData.push(com)
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
return pathData
|
|
34
|
-
}
|
|
35
|
-
|
|
36
17
|
|
|
37
18
|
|
|
38
19
|
/**
|
|
39
20
|
* Fit one or more Bezier curves to a set of pts.
|
|
40
21
|
*
|
|
41
22
|
*/
|
|
42
|
-
export function
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
} else {
|
|
49
|
-
throw Error("Not a valid point array");
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
//console.log(pts);
|
|
54
|
-
|
|
55
|
-
// Remove duplicate pts
|
|
56
|
-
pts = pts.filter(function (point, i) {
|
|
57
|
-
return i === 0 || !point.every((val, j) => {
|
|
58
|
-
return val === pts[i - 1][j];
|
|
59
|
-
});
|
|
60
|
-
});
|
|
23
|
+
export function fitCurveSchneider(pts, {
|
|
24
|
+
maxError = 0,
|
|
25
|
+
adjustCpts = true,
|
|
26
|
+
harmonize = true,
|
|
27
|
+
keepCorners = true
|
|
28
|
+
} = {}) {
|
|
61
29
|
|
|
62
30
|
if (pts.length === 1) {
|
|
63
|
-
//return [{ type: 'L', values: [pts[0][0], pts[0][1]] }];
|
|
64
31
|
return [];
|
|
65
32
|
}
|
|
66
33
|
|
|
67
|
-
|
|
68
34
|
// single lineto
|
|
69
35
|
if (pts.length === 2) {
|
|
70
36
|
return [
|
|
71
|
-
{ type: 'L', values: [pts[0]
|
|
72
|
-
{ type: 'L', values: [pts[1]
|
|
37
|
+
{ type: 'L', values: [pts[0].x, pts[0].y] },
|
|
38
|
+
{ type: 'L', values: [pts[1].x, pts[1].y] }
|
|
73
39
|
]
|
|
74
40
|
}
|
|
75
41
|
|
|
42
|
+
// prevent bulging
|
|
43
|
+
//keepCorners = true
|
|
44
|
+
//keepCorners = false
|
|
76
45
|
|
|
77
46
|
let len = pts.length;
|
|
78
|
-
|
|
79
47
|
let leftTangent = createTangent(pts[1], pts[0]);
|
|
80
48
|
let rightTangent = createTangent(pts[len - 2], pts[len - 1]);
|
|
81
|
-
|
|
82
|
-
let beziers = fitCubic(pts, leftTangent, rightTangent, maxError);
|
|
49
|
+
let beziers = fitCubic(pts, leftTangent, rightTangent, maxError, keepCorners);
|
|
83
50
|
|
|
84
51
|
// create pathdata
|
|
85
52
|
let pathData = bezierPtsToPathData(beziers)
|
|
86
53
|
|
|
87
54
|
|
|
88
55
|
|
|
89
|
-
// adjustCpts -post
|
|
90
|
-
//adjustCpts = false
|
|
91
|
-
//harmonize= false;
|
|
92
|
-
|
|
93
56
|
let cp1, cp2;
|
|
57
|
+
|
|
58
|
+
adjustCpts = false
|
|
59
|
+
harmonize = false;
|
|
60
|
+
|
|
94
61
|
if (adjustCpts) {
|
|
95
62
|
|
|
63
|
+
console.log('refine cpts');
|
|
64
|
+
|
|
96
65
|
let len2 = pathData.length;
|
|
97
66
|
let com1 = pathData[0]
|
|
98
67
|
|
|
@@ -100,9 +69,9 @@ export function fitCurveN(pts, maxError, adjustCpts = true, harmonize= true) {
|
|
|
100
69
|
let com2 = pathData[len2 - 1]
|
|
101
70
|
|
|
102
71
|
//adjust 1st and last angle
|
|
103
|
-
let p0 = { x: pts[0]
|
|
104
|
-
let p1 = { x: pts[1]
|
|
105
|
-
let p2 = pts[2] ? { x: pts[2]
|
|
72
|
+
let p0 = { x: pts[0].x, y: pts[0].y }
|
|
73
|
+
let p1 = { x: pts[1].x, y: pts[1].y }
|
|
74
|
+
let p2 = pts[2] ? { x: pts[2].x, y: pts[2].y } : null
|
|
106
75
|
|
|
107
76
|
if (p2) {
|
|
108
77
|
cp1 = { x: com1.values[0], y: com1.values[1] }
|
|
@@ -111,8 +80,8 @@ export function fitCurveN(pts, maxError, adjustCpts = true, harmonize= true) {
|
|
|
111
80
|
com1.values[1] = cp1.y
|
|
112
81
|
}
|
|
113
82
|
|
|
114
|
-
let pL = { x: pts[len - 1]
|
|
115
|
-
let pL1 = { x: pts[len - 2]
|
|
83
|
+
let pL = { x: pts[len - 1].x, y: pts[len - 1].y }
|
|
84
|
+
let pL1 = { x: pts[len - 2].x, y: pts[len - 2].y }
|
|
116
85
|
let pL2 = pts[len - 3] ? { x: pts[len - 3][0], y: pts[len - 3][1] } : null
|
|
117
86
|
|
|
118
87
|
if (pL2) {
|
|
@@ -124,13 +93,12 @@ export function fitCurveN(pts, maxError, adjustCpts = true, harmonize= true) {
|
|
|
124
93
|
|
|
125
94
|
// harmonize too tight tangents
|
|
126
95
|
//let harmonize= true;
|
|
127
|
-
if(harmonize){
|
|
128
|
-
pathData = harmonizeCubicCptsThird([{ type: 'M', values: [pts[0]
|
|
96
|
+
if (harmonize) {
|
|
97
|
+
pathData = harmonizeCubicCptsThird([{ type: 'M', values: [pts[0].x, pts[0].y] },
|
|
129
98
|
...pathData])
|
|
130
99
|
pathData.shift()
|
|
131
100
|
}
|
|
132
101
|
|
|
133
|
-
|
|
134
102
|
}
|
|
135
103
|
|
|
136
104
|
//console.log('pathData schneider', pathData);
|
|
@@ -138,109 +106,54 @@ export function fitCurveN(pts, maxError, adjustCpts = true, harmonize= true) {
|
|
|
138
106
|
}
|
|
139
107
|
|
|
140
108
|
|
|
141
|
-
function adjustTangentAngle(cp, p0, p1, p2) {
|
|
142
|
-
let ang1 = getAngle(p0, p1)
|
|
143
|
-
let ang2 = getAngle(p0, p2)
|
|
144
|
-
let angDiff = (ang2 - ang1)
|
|
145
|
-
cp = rotatePoint(cp, p0.x, p0.y, -angDiff)
|
|
146
|
-
return cp
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Use least-squares method to find Bezier control pts for region.
|
|
152
|
-
*/
|
|
153
|
-
let generateBezier = (pts, parameters, leftTangent, rightTangent) => {
|
|
154
|
-
|
|
155
|
-
//Bezier curve ctl pts
|
|
156
|
-
let a, tmp, u, ux, firstPoint = pts[0], lastPoint = pts[pts.length - 1];
|
|
157
|
-
|
|
158
|
-
let bezCurve = [firstPoint, null, null, lastPoint];
|
|
159
|
-
let A = zeros_Xx2x2(parameters.length);
|
|
160
|
-
let len = parameters.length;
|
|
161
|
-
|
|
162
|
-
for (let i = 0; i < len; i++) {
|
|
163
|
-
u = parameters[i];
|
|
164
|
-
ux = 1 - u;
|
|
165
|
-
a = A[i];
|
|
166
|
-
|
|
167
|
-
a[0] = mulItems(leftTangent, 3 * u * (ux * ux));
|
|
168
|
-
a[1] = mulItems(rightTangent, 3 * ux * (u * u));
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
//Create the C and X matrices
|
|
172
|
-
let C = [[0, 0], [0, 0]];
|
|
173
|
-
let X = [0, 0];
|
|
174
|
-
let l = pts.length;
|
|
175
|
-
|
|
176
|
-
for (let i = 0; i < l; i++) {
|
|
177
|
-
u = parameters[i];
|
|
178
|
-
a = A[i];
|
|
179
|
-
|
|
180
|
-
C[0][0] += dot(a[0], a[0]);
|
|
181
|
-
C[0][1] += dot(a[0], a[1]);
|
|
182
|
-
C[1][0] += dot(a[0], a[1]);
|
|
183
|
-
C[1][1] += dot(a[1], a[1]);
|
|
184
|
-
|
|
185
|
-
tmp = subtract(pts[i], pointAtT([firstPoint, firstPoint, lastPoint, lastPoint], u));
|
|
186
|
-
|
|
187
|
-
X[0] += dot(a[0], tmp);
|
|
188
|
-
X[1] += dot(a[1], tmp);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
//Compute the determinants of C and X
|
|
192
|
-
let det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1];
|
|
193
|
-
let det_C0_X = C[0][0] * X[1] - C[1][0] * X[0];
|
|
194
|
-
let det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1];
|
|
195
|
-
|
|
196
|
-
//Finally, derive alpha values
|
|
197
|
-
let alpha_l = det_C0_C1 === 0 ? 0 : det_X_C1 / det_C0_C1;
|
|
198
|
-
let alpha_r = det_C0_C1 === 0 ? 0 : det_C0_X / det_C0_C1;
|
|
199
|
-
let segLength = getDistance(firstPoint, lastPoint, true);
|
|
200
|
-
let epsilon = 1.0e-6 * segLength;
|
|
201
|
-
|
|
202
|
-
if (alpha_l < epsilon || alpha_r < epsilon) {
|
|
203
|
-
//Fall back on standard (probably inaccurate) formula, and subdivide further if needed.
|
|
204
|
-
bezCurve[1] = addArrays(firstPoint, mulItems(leftTangent, segLength * 0.333));
|
|
205
|
-
bezCurve[2] = addArrays(lastPoint, mulItems(rightTangent, segLength * 0.333));
|
|
206
|
-
} else {
|
|
207
|
-
// First and last control pts of the Bezier curve
|
|
208
|
-
bezCurve[1] = addArrays(firstPoint, mulItems(leftTangent, alpha_l));
|
|
209
|
-
bezCurve[2] = addArrays(lastPoint, mulItems(rightTangent, alpha_r));
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return bezCurve;
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
|
|
216
109
|
/**
|
|
217
110
|
* Fit a Bezier curve to a (sub)set of digitized pts.
|
|
218
|
-
* Your code should not call this function directly. Use
|
|
111
|
+
* Your code should not call this function directly. Use fitCurve instead.
|
|
219
112
|
*control-point-1, control-point-2, second-point] and pts are [x, y]
|
|
220
113
|
*/
|
|
221
|
-
|
|
114
|
+
function fitCubic(pts, leftTangent, rightTangent, error, keepCorners = false) {
|
|
115
|
+
|
|
116
|
+
|
|
222
117
|
//Max times to try iterating (to find an acceptable curve)
|
|
223
|
-
let MaxIterations = 20;
|
|
118
|
+
let MaxIterations = 20;
|
|
224
119
|
let bezCurve;
|
|
225
120
|
|
|
121
|
+
/*
|
|
226
122
|
//Use heuristic if region only has two pts in it
|
|
227
123
|
if (pts.length === 2) {
|
|
228
|
-
let dist = getDistance(pts[0], pts[1],
|
|
124
|
+
let dist = getDistance(pts[0], pts[1], false) * 0.333;
|
|
229
125
|
bezCurve = [pts[0], addArrays(pts[0], mulItems(leftTangent, dist)), addArrays(pts[1], mulItems(rightTangent, dist)), pts[1]];
|
|
230
126
|
return [bezCurve];
|
|
231
127
|
}
|
|
128
|
+
*/
|
|
129
|
+
|
|
232
130
|
|
|
233
131
|
//Parameterize pts, and attempt to fit curve
|
|
234
132
|
let u = chordLengthParameterize(pts);
|
|
235
133
|
let _generateAndReport = generateAndReport(pts, u, u, leftTangent, rightTangent);
|
|
236
134
|
|
|
237
135
|
bezCurve = _generateAndReport[0];
|
|
136
|
+
|
|
238
137
|
let maxError = _generateAndReport[1];
|
|
239
138
|
let splitPoint = _generateAndReport[2];
|
|
240
139
|
|
|
140
|
+
|
|
141
|
+
// check if curve is bulged
|
|
142
|
+
if (keepCorners) {
|
|
143
|
+
let checkBulge = areaDeviationTooLarge(pts, bezCurve);
|
|
144
|
+
let { isBulged, bezierNew } = checkBulge;
|
|
145
|
+
if (isBulged) {
|
|
146
|
+
// return cubic corner segment
|
|
147
|
+
bezCurve = bezierNew
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
241
152
|
if (maxError === 0 || maxError < error) {
|
|
242
153
|
return [bezCurve];
|
|
243
154
|
}
|
|
155
|
+
|
|
156
|
+
|
|
244
157
|
//If error not too large, try some reparameterization and iteration
|
|
245
158
|
if (maxError < error * error) {
|
|
246
159
|
|
|
@@ -280,28 +193,100 @@ let fitCubic = (pts, leftTangent, rightTangent, error) => {
|
|
|
280
193
|
let beziers = [];
|
|
281
194
|
let centerVector = subtract(pts[splitPoint - 1], pts[splitPoint + 1]);
|
|
282
195
|
|
|
283
|
-
if (centerVector.
|
|
284
|
-
return val === 0;
|
|
285
|
-
})) {
|
|
286
|
-
//[x,y] -> [-y,x]: http://stackoverflow.com/a/4780141/1869660
|
|
196
|
+
if (centerVector.x === 0 && centerVector.y === 0) {
|
|
287
197
|
centerVector = subtract(pts[splitPoint - 1], pts[splitPoint]);
|
|
288
|
-
let _ref =
|
|
289
|
-
centerVector
|
|
290
|
-
centerVector
|
|
198
|
+
let _ref = { x: -centerVector.y, y: centerVector.x };
|
|
199
|
+
centerVector.x = _ref.x;
|
|
200
|
+
centerVector.y = _ref.y;
|
|
291
201
|
}
|
|
292
202
|
|
|
293
203
|
let toCenterTangent = normalize(centerVector);
|
|
294
204
|
//To and from need to point in opposite directions:
|
|
295
205
|
let fromCenterTangent = mulItems(toCenterTangent, -1);
|
|
296
206
|
|
|
207
|
+
if (pts.length === 3) {
|
|
208
|
+
//splitPoint--
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
beziers.push(
|
|
212
|
+
...fitCubic(pts.slice(0, splitPoint + 1), leftTangent, toCenterTangent, error, keepCorners),
|
|
213
|
+
...fitCubic(pts.slice(splitPoint), fromCenterTangent, rightTangent, error, keepCorners)
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
|
|
297
217
|
|
|
298
|
-
beziers = beziers.concat(fitCubic(pts.slice(0, splitPoint + 1), leftTangent, toCenterTangent, error));
|
|
299
|
-
beziers = beziers.concat(fitCubic(pts.slice(splitPoint), fromCenterTangent, rightTangent, error));
|
|
300
218
|
return beziers;
|
|
301
219
|
};
|
|
302
220
|
|
|
303
221
|
|
|
304
|
-
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Use least-squares method to find Bezier control pts for region.
|
|
225
|
+
*/
|
|
226
|
+
function generateBezier(pts, parameters, leftTangent, rightTangent) {
|
|
227
|
+
|
|
228
|
+
//Bezier curve ctl pts
|
|
229
|
+
let a, tmp, u, ux, firstPoint = pts[0], lastPoint = pts[pts.length - 1];
|
|
230
|
+
|
|
231
|
+
let bezCurve = [firstPoint, null, null, lastPoint];
|
|
232
|
+
let A = zeros_Xx2x2(parameters.length);
|
|
233
|
+
let len = parameters.length;
|
|
234
|
+
|
|
235
|
+
for (let i = 0; i < len; i++) {
|
|
236
|
+
u = parameters[i];
|
|
237
|
+
ux = 1 - u;
|
|
238
|
+
a = A[i];
|
|
239
|
+
|
|
240
|
+
a[0] = mulItems(leftTangent, 3 * u * (ux * ux));
|
|
241
|
+
a[1] = mulItems(rightTangent, 3 * ux * (u * u));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
//Create the C and X matrices
|
|
245
|
+
let C = [[0, 0], [0, 0]];
|
|
246
|
+
let X = [0, 0];
|
|
247
|
+
let l = pts.length;
|
|
248
|
+
|
|
249
|
+
for (let i = 0; i < l; i++) {
|
|
250
|
+
u = parameters[i];
|
|
251
|
+
a = A[i];
|
|
252
|
+
|
|
253
|
+
C[0][0] += dot(a[0], a[0]);
|
|
254
|
+
C[0][1] += dot(a[0], a[1]);
|
|
255
|
+
C[1][0] += dot(a[0], a[1]);
|
|
256
|
+
C[1][1] += dot(a[1], a[1]);
|
|
257
|
+
|
|
258
|
+
tmp = subtract(pts[i], pointAtT([firstPoint, firstPoint, lastPoint, lastPoint], u));
|
|
259
|
+
|
|
260
|
+
X[0] += dot(a[0], tmp);
|
|
261
|
+
X[1] += dot(a[1], tmp);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
//Compute the determinants of C and X
|
|
265
|
+
let det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1];
|
|
266
|
+
let det_C0_X = C[0][0] * X[1] - C[1][0] * X[0];
|
|
267
|
+
let det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1];
|
|
268
|
+
|
|
269
|
+
//Finally, derive alpha values
|
|
270
|
+
let alpha_l = det_C0_C1 === 0 ? 0 : det_X_C1 / det_C0_C1;
|
|
271
|
+
let alpha_r = det_C0_C1 === 0 ? 0 : det_C0_X / det_C0_C1;
|
|
272
|
+
let segLength = getDistance(firstPoint, lastPoint, false);
|
|
273
|
+
let epsilon = 1.0e-6 * segLength;
|
|
274
|
+
|
|
275
|
+
if (alpha_l < epsilon || alpha_r < epsilon) {
|
|
276
|
+
//Fall back on standard (probably inaccurate) formula, and subdivide further if needed.
|
|
277
|
+
bezCurve[1] = addArrays(firstPoint, mulItems(leftTangent, segLength * 0.333));
|
|
278
|
+
bezCurve[2] = addArrays(lastPoint, mulItems(rightTangent, segLength * 0.333));
|
|
279
|
+
} else {
|
|
280
|
+
// First and last control pts of the Bezier curve
|
|
281
|
+
bezCurve[1] = addArrays(firstPoint, mulItems(leftTangent, alpha_l));
|
|
282
|
+
bezCurve[2] = addArrays(lastPoint, mulItems(rightTangent, alpha_r));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return bezCurve;
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
function generateAndReport(pts, paramsOrig, paramsPrime, leftTangent, rightTangent) {
|
|
305
290
|
let bezCurve, maxError, splitPoint;
|
|
306
291
|
|
|
307
292
|
bezCurve = generateBezier(pts, paramsPrime, leftTangent, rightTangent);
|
|
@@ -337,8 +322,8 @@ function newtonRaphsonRootFind(bez, point, u) {
|
|
|
337
322
|
//let q = bezierQ(bez, u);
|
|
338
323
|
let q = pointAtT(bez, u)
|
|
339
324
|
|
|
340
|
-
let dx = q
|
|
341
|
-
let dy = q
|
|
325
|
+
let dx = q.x - point.x;
|
|
326
|
+
let dy = q.y - point.y;
|
|
342
327
|
|
|
343
328
|
// First derivative (tangent vector at u)
|
|
344
329
|
let qp = bezierQprime(bez, u);
|
|
@@ -388,7 +373,7 @@ function chordLengthParameterize(pts) {
|
|
|
388
373
|
for (let i = 0; i < l; i++) {
|
|
389
374
|
p = pts[i];
|
|
390
375
|
//currU = i ? prevU + length(subtract(p, p0)) : 0;
|
|
391
|
-
currU = prevU + getDistance(p, p0,
|
|
376
|
+
currU = prevU + getDistance(p, p0, false);
|
|
392
377
|
u.push(currU);
|
|
393
378
|
prevU = currU;
|
|
394
379
|
|
|
@@ -420,16 +405,24 @@ function computeMaxError(pts, bez, parameters) {
|
|
|
420
405
|
|
|
421
406
|
let t_distMap = mapTtoRelativeDistances(bez, 10);
|
|
422
407
|
let l = pts.length
|
|
408
|
+
let ptOnPath = null
|
|
423
409
|
|
|
424
410
|
for (i = 0; i < l; i++) {
|
|
425
411
|
point = pts[i];
|
|
426
412
|
//Find 't' for a point on the bez curve that's as close to 'point' as possible:
|
|
427
413
|
t = find_t(parameters[i], t_distMap, 10);
|
|
428
414
|
|
|
415
|
+
ptOnPath = pointAtT(bez, t);
|
|
416
|
+
dist = getSquareDistance(ptOnPath, point)
|
|
417
|
+
|
|
418
|
+
/*
|
|
419
|
+
console.log('v', v);
|
|
429
420
|
v = subtract(pointAtT(bez, t), point);
|
|
430
|
-
dist = v
|
|
421
|
+
dist = v.x * v.x + v.y * v.y;
|
|
422
|
+
*/
|
|
431
423
|
|
|
432
424
|
if (dist > maxDist) {
|
|
425
|
+
//renderPoint(markers, ptOnPath)
|
|
433
426
|
maxDist = dist;
|
|
434
427
|
splitPoint = i;
|
|
435
428
|
}
|
|
@@ -447,13 +440,13 @@ function mapTtoRelativeDistances(bez, B_parts) {
|
|
|
447
440
|
|
|
448
441
|
for (let i = 1; i <= B_parts; i++) {
|
|
449
442
|
B_t_curr = pointAtT(bez, i / B_parts);
|
|
450
|
-
sumLen += getDistance(B_t_curr, B_t_prev
|
|
443
|
+
sumLen += getDistance(B_t_curr, B_t_prev);
|
|
451
444
|
B_t_dist.push(sumLen);
|
|
452
445
|
B_t_prev = B_t_curr;
|
|
453
446
|
}
|
|
454
447
|
|
|
455
448
|
//Normalize B_length to the same interval as the parameter distances; 0 to 1:
|
|
456
|
-
B_t_dist = B_t_dist.map(
|
|
449
|
+
B_t_dist = B_t_dist.map((x) => {
|
|
457
450
|
return x / sumLen;
|
|
458
451
|
});
|
|
459
452
|
return B_t_dist;
|
|
@@ -486,17 +479,80 @@ function find_t(param, t_distMap, B_parts) {
|
|
|
486
479
|
return t;
|
|
487
480
|
}
|
|
488
481
|
|
|
482
|
+
|
|
483
|
+
function areaDeviationTooLarge(pts, bezCurve) {
|
|
484
|
+
|
|
485
|
+
let split = 4;
|
|
486
|
+
let step = 1 / split;
|
|
487
|
+
let l = pts.length
|
|
488
|
+
|
|
489
|
+
// create polygon from curve candidate
|
|
490
|
+
let poly = [bezCurve[0]];
|
|
491
|
+
let pt;
|
|
492
|
+
|
|
493
|
+
for (let i = 1; i < split; i++) {
|
|
494
|
+
let t = step * i
|
|
495
|
+
pt = pointAtT(bezCurve, t)
|
|
496
|
+
poly.push(pt);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
poly.push(bezCurve[bezCurve.length - 1]);
|
|
500
|
+
|
|
501
|
+
// Original area
|
|
502
|
+
let polyArea = getPolygonArea(pts, false)
|
|
503
|
+
|
|
504
|
+
// flat line
|
|
505
|
+
if (!polyArea && pts.length === 2) {
|
|
506
|
+
polyArea = getSquareDistance(pts[0], pts[1]) * 0.01
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
let curveArea = getPolygonArea(poly, false);
|
|
510
|
+
let rat = curveArea / polyArea;
|
|
511
|
+
|
|
512
|
+
let isBulged = rat > 1.1
|
|
513
|
+
//|| rat < 0.9
|
|
514
|
+
// isBulged = rat > 2
|
|
515
|
+
let bezierNew = bezCurve
|
|
516
|
+
if (isBulged) {
|
|
517
|
+
let ptMid = pts[Math.floor(l * 0.5)]
|
|
518
|
+
let p = pts[l - 1];
|
|
519
|
+
/*
|
|
520
|
+
let cp1 = pointAtT([pts[0], ptMid], 0.666);
|
|
521
|
+
let cp2 = pointAtT([p, ptMid], 0.666);
|
|
522
|
+
//let ptMid2 = pts[Math.floor(pts.length*0.666)]
|
|
523
|
+
bezierNew = [pts[0], cp1, cp2, p]
|
|
524
|
+
//bezierNew = [pts[0], ptMid, ptMid, p]
|
|
525
|
+
|
|
526
|
+
//renderPoint(markers, cp1, 'cyan')
|
|
527
|
+
//renderPoint(markers, cp2, 'cyan')
|
|
528
|
+
//console.log('bezCurve', bezCurve, pts);
|
|
529
|
+
|
|
530
|
+
cp1 = pointAtT([bezCurve[0], bezCurve[3]], 0.333);
|
|
531
|
+
cp2 = pointAtT([bezCurve[0], bezCurve[3]], 0.666);
|
|
532
|
+
bezCurve = [bezCurve[0], cp1, cp2, bezCurve[3]]
|
|
533
|
+
*/
|
|
534
|
+
|
|
535
|
+
//console.log('!!!bulged');
|
|
536
|
+
|
|
537
|
+
bezCurve = [pts[0], ptMid, ptMid, p]
|
|
538
|
+
bezierNew = bezCurve
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return { isBulged, bezierNew };
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
|
|
489
545
|
/**
|
|
490
546
|
* Creates a vector of length 1 which shows the direction from B to A
|
|
491
547
|
*/
|
|
492
548
|
function createTangent(p1, p2) {
|
|
493
549
|
// Returns unit vector pointing from B to A
|
|
494
|
-
let dx = p1
|
|
495
|
-
let dy = p1
|
|
550
|
+
let dx = p1.x - p2.x;
|
|
551
|
+
let dy = p1.y - p2.y;
|
|
496
552
|
let length = Math.sqrt(dx * dx + dy * dy);
|
|
497
553
|
|
|
498
|
-
if (length === 0) return
|
|
499
|
-
return
|
|
554
|
+
if (length === 0) return { x: 0, y: 0 };
|
|
555
|
+
return { x: dx / length, y: dy / length };
|
|
500
556
|
}
|
|
501
557
|
|
|
502
558
|
|
|
@@ -506,26 +562,26 @@ function createTangent(p1, p2) {
|
|
|
506
562
|
|
|
507
563
|
// Basic vector utilities (only what's absolutely necessary)
|
|
508
564
|
function subtract(a, b) {
|
|
509
|
-
return
|
|
565
|
+
return { x: a.x - b.x, y: a.y - b.y };
|
|
510
566
|
}
|
|
511
567
|
|
|
512
568
|
function addArrays(a, b) {
|
|
513
|
-
return
|
|
569
|
+
return { x: a.x + b.x, y: a.y + b.y };
|
|
514
570
|
}
|
|
515
571
|
|
|
516
572
|
|
|
517
573
|
function mulItems(v, s) {
|
|
518
|
-
return
|
|
574
|
+
return { x: v.x * s, y: v.y * s };
|
|
519
575
|
}
|
|
520
576
|
|
|
521
577
|
|
|
522
578
|
function normalize(v) {
|
|
523
|
-
let len = Math.sqrt(v
|
|
524
|
-
return len === 0 ?
|
|
579
|
+
let len = Math.sqrt(v.x * v.x + v.y * v.y);
|
|
580
|
+
return len === 0 ? { x: 0, y: 0 } : { x: v.x / len, y: v.y / len };
|
|
525
581
|
}
|
|
526
582
|
|
|
527
583
|
function dot(a, b) {
|
|
528
|
-
return a
|
|
584
|
+
return a.x * b.x + a.y * b.y;
|
|
529
585
|
}
|
|
530
586
|
|
|
531
587
|
function zeros_Xx2x2(x) {
|
|
@@ -547,24 +603,59 @@ function bezierQprime(bez, u, second = false) {
|
|
|
547
603
|
let dx, dy;
|
|
548
604
|
|
|
549
605
|
if (second) {
|
|
550
|
-
dx = 6 * mt * (cp2
|
|
551
|
-
6 * t * (p1
|
|
606
|
+
dx = 6 * mt * (cp2.x - 2 * cp1.x + p0.x) +
|
|
607
|
+
6 * t * (p1.x - 2 * cp2.x + cp1.x);
|
|
552
608
|
|
|
553
|
-
dy = 6 * mt * (cp2
|
|
554
|
-
6 * t * (p1
|
|
609
|
+
dy = 6 * mt * (cp2.y - 2 * cp1.y + p0.y) +
|
|
610
|
+
6 * t * (p1.y - 2 * cp2.y + cp1.y);
|
|
555
611
|
|
|
556
612
|
} else {
|
|
557
|
-
dx = 3 * mt2 * (cp1
|
|
558
|
-
6 * mt * t * (cp2
|
|
559
|
-
3 * t2 * (p1
|
|
613
|
+
dx = 3 * mt2 * (cp1.x - p0.x) +
|
|
614
|
+
6 * mt * t * (cp2.x - cp1.x) +
|
|
615
|
+
3 * t2 * (p1.x - cp2.x);
|
|
560
616
|
|
|
561
|
-
dy = 3 * mt2 * (cp1
|
|
562
|
-
6 * mt * t * (cp2
|
|
563
|
-
3 * t2 * (p1
|
|
617
|
+
dy = 3 * mt2 * (cp1.y - p0.y) +
|
|
618
|
+
6 * mt * t * (cp2.y - cp1.y) +
|
|
619
|
+
3 * t2 * (p1.y - cp2.y);
|
|
564
620
|
}
|
|
565
621
|
|
|
566
622
|
return [dx, dy];
|
|
567
623
|
}
|
|
568
624
|
|
|
569
625
|
|
|
626
|
+
function adjustTangentAngle(cp, p0, p1, p2) {
|
|
627
|
+
let ang1 = getAngle(p0, p1)
|
|
628
|
+
let ang2 = getAngle(p0, p2)
|
|
629
|
+
let angDiff = (ang2 - ang1)
|
|
630
|
+
cp = rotatePoint(cp, p0.x, p0.y, -angDiff)
|
|
631
|
+
return cp
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
// convert to pathdata
|
|
637
|
+
function bezierPtsToPathData(beziers = []) {
|
|
638
|
+
let pathData = [];
|
|
639
|
+
beziers.forEach(bez => {
|
|
640
|
+
|
|
641
|
+
let type = bez.length === 4 ? 'C' : (bez.length === 3 ? 'Q' : 'L')
|
|
642
|
+
|
|
643
|
+
let cp1 = type === 'C' || type === 'Q' ? bez[1] : null;
|
|
644
|
+
let cp2 = type === 'C' ? bez[2] : null;
|
|
645
|
+
let p = bez[bez.length - 1]
|
|
646
|
+
|
|
647
|
+
let values = type === 'C' ?
|
|
648
|
+
[cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y] :
|
|
649
|
+
(type === 'Q' ?
|
|
650
|
+
[cp1.x, cp1.y, p.x, p.y] :
|
|
651
|
+
[p.x, p.y]
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
let com = { type, values }
|
|
655
|
+
pathData.push(com)
|
|
656
|
+
})
|
|
657
|
+
|
|
658
|
+
return pathData
|
|
659
|
+
}
|
|
660
|
+
|
|
570
661
|
|