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
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { findPathDataIntersections } from "./pathData_get_intersections";
|
|
2
|
+
import { getParallelPoly } from "./poly-offset";
|
|
3
|
+
import { checkLineIntersection, pointAtT } from "./svgii/geometry";
|
|
4
|
+
import { getPolygonArea } from "./svgii/geometry_area";
|
|
5
|
+
import { pathDataToD } from "./svgii/pathData_stringify";
|
|
6
|
+
import { renderPath, renderPoint, renderPoly } from "./svgii/visualize";
|
|
7
|
+
|
|
8
|
+
export function offsetPathData(pathData = [], strokeWidth = 18, side = top) {
|
|
9
|
+
|
|
10
|
+
console.log('offsetPathData');
|
|
11
|
+
//alert('off')
|
|
12
|
+
|
|
13
|
+
let pathDataN = [];
|
|
14
|
+
let l = pathData.length;
|
|
15
|
+
let closed = pathData[l - 1].type.toLowerCase() === 'z'
|
|
16
|
+
|
|
17
|
+
for (let i = 0; i < l; i++) {
|
|
18
|
+
let comPrev = i > 0 ? pathData[i - 1] : null;
|
|
19
|
+
let com = pathData[i];
|
|
20
|
+
let { type, values, p0, cp1, cp2, p } = com;
|
|
21
|
+
//let comN = com
|
|
22
|
+
|
|
23
|
+
if (type === 'C') {
|
|
24
|
+
|
|
25
|
+
//split path
|
|
26
|
+
let segments = pointAtT([p0, cp1, cp2, p], 0.5, false, true).segments;
|
|
27
|
+
//console.log(segments);
|
|
28
|
+
|
|
29
|
+
let pts = [segments[0].p0];
|
|
30
|
+
segments.forEach(com => {
|
|
31
|
+
let { p0, cp1, cp2, p } = com;
|
|
32
|
+
|
|
33
|
+
pts.push(cp1, cp2, p)
|
|
34
|
+
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
let area = getPolygonArea(pts)
|
|
38
|
+
let sweep = area < 0 ? 0 : 1
|
|
39
|
+
let polyOffset = getParallelPoly(pts, strokeWidth, sweep, closed)
|
|
40
|
+
let { polyTop, polyBottom } = polyOffset
|
|
41
|
+
//console.log(polyOffset);
|
|
42
|
+
|
|
43
|
+
com.polyTop = polyTop
|
|
44
|
+
com.polyBottom = polyBottom
|
|
45
|
+
com.t = 0
|
|
46
|
+
com.segs = []
|
|
47
|
+
com.inter = null
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
// convert pts to cpts
|
|
51
|
+
let pathData2 = [
|
|
52
|
+
{ type: 'M', values: [polyBottom[0].x, polyBottom[0].y] }
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
p0 = polyBottom[0];
|
|
56
|
+
for (let j = 3; j < polyBottom.length; j += 3) {
|
|
57
|
+
let [cp1, cp2, p] = [polyBottom[j - 2], polyBottom[j - 1], polyBottom[j]]
|
|
58
|
+
pathData2.push(
|
|
59
|
+
{ type: 'C', values: [cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y] }
|
|
60
|
+
)
|
|
61
|
+
com.segs.push({ p0, cp1, cp2, p })
|
|
62
|
+
p0 = p;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let d = pathDataToD(pathData2);
|
|
66
|
+
renderPath(markers, d, '#ccc')
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if (comPrev && comPrev.polyBottom) {
|
|
70
|
+
let polyprev = comPrev.polyBottom
|
|
71
|
+
console.log(comPrev);
|
|
72
|
+
let pt1 = polyprev[polyprev.length - 1]
|
|
73
|
+
let pt2 = polyprev[polyprev.length - 2]
|
|
74
|
+
|
|
75
|
+
let pt3 = polyBottom[0]
|
|
76
|
+
let pt4 = polyBottom[1]
|
|
77
|
+
|
|
78
|
+
let ptI = checkLineIntersection(pt4, pt3, pt2, pt1)
|
|
79
|
+
if (ptI) {
|
|
80
|
+
//console.log(ptI);
|
|
81
|
+
//comPrev.t = ptI.t2 * 1.2
|
|
82
|
+
comPrev.segs[comPrev.segs.length - 1].t = ptI.t2 * 1.2
|
|
83
|
+
|
|
84
|
+
comPrev.t = ptI.t2 * 1.2
|
|
85
|
+
com.t = 1 - ptI.t2 * 1.2
|
|
86
|
+
com.segs[0].t = 1 - ptI.t2 * 1.2
|
|
87
|
+
|
|
88
|
+
comPrev.inter = ptI
|
|
89
|
+
//com.inter = ptI
|
|
90
|
+
|
|
91
|
+
//console.log('com.t', com.t);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// adjust intersection
|
|
95
|
+
// comPrev.polyBottom[polyprev.length-1] = ptI
|
|
96
|
+
|
|
97
|
+
polyprev[polyprev.length - 1] = ptI
|
|
98
|
+
polyBottom[0] = ptI
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
/*
|
|
102
|
+
renderPoint(markers, pt1, 'purple')
|
|
103
|
+
renderPoint(markers, pt2, 'magenta')
|
|
104
|
+
renderPoint(markers, pt3, 'green')
|
|
105
|
+
renderPoint(markers, pt4, 'cyan')
|
|
106
|
+
renderPoint(markers, ptI, 'red')
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
//pathDataN.push(comN)
|
|
116
|
+
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
//console.log(pathData);
|
|
121
|
+
|
|
122
|
+
// convert pts to cpts
|
|
123
|
+
/*
|
|
124
|
+
pathDataN = [
|
|
125
|
+
{ type: 'M', values: [pathData[1].polyBottom[0].x, pathData[1].polyBottom[0].y] }
|
|
126
|
+
]
|
|
127
|
+
*/
|
|
128
|
+
|
|
129
|
+
pathDataN = [
|
|
130
|
+
{ type: 'M', values: [pathData[0].p0.x, pathData[0].p0.y] }
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
console.log(pathData);
|
|
135
|
+
|
|
136
|
+
for (let i = 1; i < l; i++) {
|
|
137
|
+
let com = pathData[i];
|
|
138
|
+
let comPrev = pathData[i - 1];
|
|
139
|
+
let comN = pathData[i+1] ? pathData[i+1] : null;
|
|
140
|
+
let { type, values, p0 = null, cp1 = null, cp2 = null, p, polyTop = null, polyBottom = null, segs = [], inter = null, t = 0 } = com;
|
|
141
|
+
|
|
142
|
+
// has intersection
|
|
143
|
+
if (inter) {
|
|
144
|
+
|
|
145
|
+
let s = t > 0.5 ? segs[0] : segs[1]
|
|
146
|
+
let s2 = t > 0.5 ? comN.segs[1] : comN.segs[0]
|
|
147
|
+
//let s2 =
|
|
148
|
+
|
|
149
|
+
console.log('segs', i, segs, s);
|
|
150
|
+
|
|
151
|
+
let pd1 = [
|
|
152
|
+
{ type: 'M', values: [s.p0.x, s.p0.y] },
|
|
153
|
+
{ type: 'C', values: [s.cp1.x, s.cp1.y, s.cp2.x, s.cp2.y, s.p.x, s.p.y] }
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
let pd2 = [
|
|
157
|
+
{ type: 'M', values: [s2.p0.x, s2.p0.y] },
|
|
158
|
+
{ type: 'C', values: [s2.cp1.x, s2.cp1.y, s2.cp2.x, s2.cp2.y, s2.p.x, s2.p.y] }
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
let d1 = pathDataToD(pd1);
|
|
163
|
+
renderPath(markers, d1, 'purple')
|
|
164
|
+
|
|
165
|
+
let d2 = pathDataToD(pd2);
|
|
166
|
+
renderPath(markers, d2, 'orange')
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
// find intersections
|
|
170
|
+
let intersections = findPathDataIntersections(pd1, pd2)
|
|
171
|
+
console.log('inter', intersections);
|
|
172
|
+
|
|
173
|
+
intersections.forEach(int=>{
|
|
174
|
+
let ptI1=pointAtT(int.cpts1, int.t1)
|
|
175
|
+
let ptI2=pointAtT(int.cpts2, int.t2)
|
|
176
|
+
|
|
177
|
+
renderPoint(markers, ptI1, 'orange', '2%')
|
|
178
|
+
renderPoint(markers, ptI2, 'red', '1%')
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
//renderPoint(markers, inter)
|
|
185
|
+
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
pathDataN.push(com)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
//let d = pathDataToD(pathDataN);
|
|
195
|
+
//renderPath(markers, d, 'red')
|
|
196
|
+
//console.log(d);
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
return pathDataN
|
|
201
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { getCombinedByDominant } from "./pathData_simplify_cubic_extrapolate";
|
|
2
|
-
import { getDistance, getSquareDistance, checkLineIntersection, pointAtT, interpolate, getDistManhattan } from "./svgii/geometry";
|
|
2
|
+
import { getDistance, getSquareDistance, checkLineIntersection, pointAtT, interpolate, getDistManhattan, getPerpendicularPoint } from "./svgii/geometry";
|
|
3
3
|
import { getBezierArea, getPolygonArea } from "./svgii/geometry_area";
|
|
4
|
-
import {
|
|
4
|
+
import { pathDataToD } from "./svgii/pathData_stringify";
|
|
5
|
+
import { renderPath, renderPoint, renderPoly } from "./svgii/visualize";
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
|
|
@@ -43,10 +44,13 @@ export function simplifyPathDataCubic(pathData, {
|
|
|
43
44
|
|
|
44
45
|
// try simplification
|
|
45
46
|
else {
|
|
47
|
+
|
|
48
|
+
|
|
46
49
|
//renderPoint(markers, p, 'magenta', '1%')
|
|
47
50
|
let combined = combineCubicPairs(com, comN, { tolerance })
|
|
48
51
|
let error = 0;
|
|
49
52
|
|
|
53
|
+
|
|
50
54
|
// combining successful! try next segment
|
|
51
55
|
if (combined.length === 1) {
|
|
52
56
|
com = combined[0]
|
|
@@ -134,12 +138,13 @@ export function combineCubicPairs(com1, com2, {
|
|
|
134
138
|
// quit if t is start
|
|
135
139
|
if (!t) return commands;
|
|
136
140
|
|
|
141
|
+
// get averaged threshold
|
|
137
142
|
let distAv1 = getDistManhattan(com1.p0, com1.p);
|
|
138
143
|
let distAv2 = getDistManhattan(com2.p0, com2.p);
|
|
139
144
|
let distMin = Math.max(0, Math.min(distAv1, distAv2))
|
|
140
145
|
|
|
141
|
-
|
|
142
|
-
let distScale = 0.
|
|
146
|
+
//let distScale = 0.08
|
|
147
|
+
let distScale = 0.075
|
|
143
148
|
let maxDist = distMin * distScale * tolerance
|
|
144
149
|
|
|
145
150
|
// get hypothetical combined command
|
|
@@ -148,7 +153,6 @@ export function combineCubicPairs(com1, com2, {
|
|
|
148
153
|
// test new point-at-t against original mid segment starting point
|
|
149
154
|
let ptI = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t)
|
|
150
155
|
|
|
151
|
-
|
|
152
156
|
let dist0 = getDistManhattan(com1.p, ptI)
|
|
153
157
|
let dist1 = 0, dist2 = 0;
|
|
154
158
|
let close = dist0 < maxDist;
|
|
@@ -157,7 +161,6 @@ export function combineCubicPairs(com1, com2, {
|
|
|
157
161
|
// collect error data
|
|
158
162
|
let error = dist0;
|
|
159
163
|
|
|
160
|
-
|
|
161
164
|
if (close) {
|
|
162
165
|
|
|
163
166
|
/**
|
|
@@ -173,7 +176,6 @@ export function combineCubicPairs(com1, com2, {
|
|
|
173
176
|
let ptI_seg1 = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t2)
|
|
174
177
|
dist1 = getDistManhattan(ptM_seg1, ptI_seg1)
|
|
175
178
|
|
|
176
|
-
|
|
177
179
|
error += dist1;
|
|
178
180
|
|
|
179
181
|
if (dist1 < maxDist) {
|
|
@@ -190,17 +192,6 @@ export function combineCubicPairs(com1, com2, {
|
|
|
190
192
|
|
|
191
193
|
if (error < maxDist) success = true;
|
|
192
194
|
|
|
193
|
-
|
|
194
|
-
/*
|
|
195
|
-
renderPoint(markers, ptM_seg1, 'cyan')
|
|
196
|
-
renderPoint(markers, pt, 'orange', '1.5%', '1')
|
|
197
|
-
renderPoint(markers, ptM_seg2, 'orange')
|
|
198
|
-
|
|
199
|
-
renderPoint(markers, com1.p, 'green')
|
|
200
|
-
//renderPoint(markers, com2.p, 'green')
|
|
201
|
-
renderPoint(markers, ptI_seg1, 'purple')
|
|
202
|
-
*/
|
|
203
|
-
|
|
204
195
|
}
|
|
205
196
|
|
|
206
197
|
} // end 1st try
|
|
@@ -221,21 +212,6 @@ export function combineCubicPairs(com1, com2, {
|
|
|
221
212
|
comS.directionChange = com2.directionChange;
|
|
222
213
|
comS.corner = com2.corner;
|
|
223
214
|
|
|
224
|
-
|
|
225
|
-
if (comS.extreme || comS.corner) {
|
|
226
|
-
//renderPoint(markers, comS.p)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/*
|
|
230
|
-
comS.extreme = com1.extreme;
|
|
231
|
-
comS.directionChange = com1.directionChange;
|
|
232
|
-
comS.corner = com1.corner;
|
|
233
|
-
*/
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
//comS.directionChange = com1.directionChange ? true : (com2.directionChange);
|
|
238
|
-
|
|
239
215
|
comS.values = [comS.cp1.x, comS.cp1.y, comS.cp2.x, comS.cp2.y, comS.p.x, comS.p.y]
|
|
240
216
|
|
|
241
217
|
// relative error
|
|
@@ -308,6 +284,7 @@ export function findSplitT(com1, com2) {
|
|
|
308
284
|
// distances between 1st and 2nd segment cpt to mid point
|
|
309
285
|
let l1 = getDistManhattan(com1.cp2, com1.p)
|
|
310
286
|
|
|
287
|
+
|
|
311
288
|
// exit for zero length control point vectors
|
|
312
289
|
if (l1 === 0) {
|
|
313
290
|
//console.log('!quit1');
|
|
@@ -331,5 +308,8 @@ export function findSplitT(com1, com2) {
|
|
|
331
308
|
}
|
|
332
309
|
*/
|
|
333
310
|
|
|
334
|
-
|
|
311
|
+
let t = l1 / l3;
|
|
312
|
+
//console.log('t', t);
|
|
313
|
+
|
|
314
|
+
return t;
|
|
335
315
|
}
|
|
@@ -1,10 +1,147 @@
|
|
|
1
|
-
import { checkLineIntersection, getAngle, getDistAv, getSquareDistance, interpolate, pointAtT, rotatePoint } from "./svgii/geometry";
|
|
1
|
+
import { checkLineIntersection, getAngle, getDistance, getDistAv, getDistManhattan, getSquareDistance, interpolate, pointAtT, rotatePoint } from "./svgii/geometry";
|
|
2
2
|
import { getPathArea } from "./svgii/geometry_area";
|
|
3
3
|
import { pathDataToD } from "./svgii/pathData_stringify";
|
|
4
4
|
import { renderPath, renderPoint } from "./svgii/visualize";
|
|
5
5
|
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
6
10
|
export function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, debug = false) {
|
|
7
11
|
|
|
12
|
+
// if combining fails return original commands
|
|
13
|
+
let commands = [com1, com2]
|
|
14
|
+
|
|
15
|
+
// detect dominant
|
|
16
|
+
let dist1 = getDistManhattan(com1.p0, com1.p)
|
|
17
|
+
let dist2 = getDistManhattan(com2.p0, com2.p)
|
|
18
|
+
let thresh = (dist1 + dist2) * 0.5 * 0.075 * tolerance
|
|
19
|
+
|
|
20
|
+
// take longer command
|
|
21
|
+
let reverse = dist1 < dist2;
|
|
22
|
+
//let reverse = dist1 > dist2;
|
|
23
|
+
|
|
24
|
+
// backup original commands
|
|
25
|
+
let com1_o = JSON.parse(JSON.stringify(com1))
|
|
26
|
+
let com2_o = JSON.parse(JSON.stringify(com2))
|
|
27
|
+
|
|
28
|
+
// intersection of control tangents
|
|
29
|
+
let ptI = checkLineIntersection(com1_o.p0, com1_o.cp1, com2_o.p, com2_o.cp2, false, true)
|
|
30
|
+
|
|
31
|
+
// no intersection - we can't combine
|
|
32
|
+
if (!ptI) {
|
|
33
|
+
return commands
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (reverse) {
|
|
37
|
+
let com2_R = {
|
|
38
|
+
p0: { x: com1.p.x, y: com1.p.y },
|
|
39
|
+
cp1: { x: com1.cp2.x, y: com1.cp2.y },
|
|
40
|
+
cp2: { x: com1.cp1.x, y: com1.cp1.y },
|
|
41
|
+
p: { x: com1.p0.x, y: com1.p0.y },
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let com1_R = {
|
|
45
|
+
p0: { x: com2.p.x, y: com2.p.y },
|
|
46
|
+
cp1: { x: com2.cp2.x, y: com2.cp2.y },
|
|
47
|
+
cp2: { x: com2.cp1.x, y: com2.cp1.y },
|
|
48
|
+
p: { x: com2.p0.x, y: com2.p0.y },
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
com1 = com1_R;
|
|
52
|
+
com2 = com2_R;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
// etsimate t for extrapolation
|
|
57
|
+
let PtI = checkLineIntersection(com1.cp2, com1.p, com2.p, com2.cp2, false, true)
|
|
58
|
+
let cp1_I = interpolate(com1.p, PtI, 0.666)
|
|
59
|
+
|
|
60
|
+
//renderPoint(markers, cp1_I)
|
|
61
|
+
//renderPoint(markers, com1.p0, 'green', '2%')
|
|
62
|
+
|
|
63
|
+
let dist1_2 = getDistManhattan(com1.cp2, com1.p)
|
|
64
|
+
let dist2_2 = getDistManhattan(com1.cp2, cp1_I)
|
|
65
|
+
let t = dist2_2 / dist1_2
|
|
66
|
+
|
|
67
|
+
// extrapolate
|
|
68
|
+
let segs = pointAtT([com1.p0, com1.cp1, com1.cp2, com1.p], t, false, true).segments
|
|
69
|
+
|
|
70
|
+
//console.log(segs);
|
|
71
|
+
let seg = segs[0]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
// if it worked - points should be nearby
|
|
75
|
+
let dist = getDistManhattan(seg.p, com2.p)
|
|
76
|
+
|
|
77
|
+
// if close enough adjust
|
|
78
|
+
if (dist < thresh) {
|
|
79
|
+
let angle = getAngle(seg.p, seg.cp2)
|
|
80
|
+
let angle2 = getAngle(com2.p, com2.cp2)
|
|
81
|
+
//let angleDiff = Math.abs(angle2 - angle)
|
|
82
|
+
let angleDiff = (angle2 - angle)
|
|
83
|
+
|
|
84
|
+
//let angleRat = angle / angle2;
|
|
85
|
+
//angleRat = (1-angleDiff) / angle2;
|
|
86
|
+
|
|
87
|
+
// adjust cp angle
|
|
88
|
+
seg.cp2 = rotatePoint(seg.cp2, seg.p.x, seg.p.y, angleDiff)
|
|
89
|
+
let dist1 = getDistManhattan(seg.p, seg.cp2)
|
|
90
|
+
|
|
91
|
+
// copy original final point coordinates
|
|
92
|
+
seg.p = com2.p
|
|
93
|
+
|
|
94
|
+
// after rotation
|
|
95
|
+
let dist2 = getDistManhattan(seg.p, seg.cp2)
|
|
96
|
+
let scale = dist2 / dist1
|
|
97
|
+
|
|
98
|
+
// adjust tangent length
|
|
99
|
+
seg.cp2 = interpolate(seg.p, seg.cp2, scale)
|
|
100
|
+
|
|
101
|
+
// reverse back
|
|
102
|
+
if (reverse) {
|
|
103
|
+
seg = {
|
|
104
|
+
p0: seg.p,
|
|
105
|
+
p: seg.p0,
|
|
106
|
+
cp1: seg.cp2,
|
|
107
|
+
cp2: seg.cp1,
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
commands = [
|
|
112
|
+
{
|
|
113
|
+
type: 'C',
|
|
114
|
+
values: [seg.cp1.x, seg.cp1.y, seg.cp2.x, seg.cp2.y, seg.p.x, seg.p.y],
|
|
115
|
+
p0: seg.p0,
|
|
116
|
+
cp1: seg.cp1,
|
|
117
|
+
cp2: seg.cp2,
|
|
118
|
+
p: seg.p,
|
|
119
|
+
extreme: com2_o.extreme,
|
|
120
|
+
corner: com2_o.corner,
|
|
121
|
+
directionChange: com2_o.directionChange,
|
|
122
|
+
dimA: getDistManhattan(seg.p0, seg.p),
|
|
123
|
+
error: dist
|
|
124
|
+
|
|
125
|
+
}
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
//console.log('commands', reverse, commands);
|
|
131
|
+
return commands
|
|
132
|
+
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
export function getCombinedByDominant_back(com1, com2, maxDist = 0, tolerance = 1, debug = false) {
|
|
142
|
+
|
|
143
|
+
//console.log('getCombinedByDominant');
|
|
144
|
+
|
|
8
145
|
// cubic Bézier derivative
|
|
9
146
|
const cubicDerivative = (p0, p1, p2, p3, t) => {
|
|
10
147
|
let mt = 1 - t;
|
|
@@ -25,8 +162,8 @@ export function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, de
|
|
|
25
162
|
let commands = [com1, com2]
|
|
26
163
|
|
|
27
164
|
// detect dominant
|
|
28
|
-
let dist1 =
|
|
29
|
-
let dist2 =
|
|
165
|
+
let dist1 = getDistManhattan(com1.p0, com1.p)
|
|
166
|
+
let dist2 = getDistManhattan(com2.p0, com2.p)
|
|
30
167
|
|
|
31
168
|
let reverse = dist1 > dist2;
|
|
32
169
|
|
|
@@ -91,10 +228,12 @@ export function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, de
|
|
|
91
228
|
let dP = cubicDerivative(com2.p0, com2.cp1, com2.cp2, com2.p, t0);
|
|
92
229
|
let r = sub(P, com1.p0);
|
|
93
230
|
|
|
94
|
-
//let t0_2 = t0 - dot(r, dP) / dot(dP, dP);
|
|
95
231
|
|
|
232
|
+
//let t0_2 = t0 - dot(r, dP) / dot(dP, dP);
|
|
96
233
|
t0 -= dot(r, dP) / dot(dP, dP);
|
|
97
234
|
|
|
235
|
+
//console.log(t0, t2);
|
|
236
|
+
|
|
98
237
|
// construct merged cubic over [t0, 1]
|
|
99
238
|
let Q0 = pointAtT([com2.p0, com2.cp1, com2.cp2, com2.p], t0);
|
|
100
239
|
let Q3 = com2.p;
|
|
@@ -145,8 +284,8 @@ export function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, de
|
|
|
145
284
|
|
|
146
285
|
//let tscale =(1 + t0)
|
|
147
286
|
//console.log('tscale', tscale);
|
|
148
|
-
let cp1_2 = interpolate(result.p0, ptI_1, 1.333
|
|
149
|
-
let cp2_2 = interpolate(result.p, ptI_2, 1.333
|
|
287
|
+
let cp1_2 = interpolate(result.p0, ptI_1, 1.333)
|
|
288
|
+
let cp2_2 = interpolate(result.p, ptI_2, 1.333)
|
|
150
289
|
|
|
151
290
|
// test self intersections and exit
|
|
152
291
|
let cp_intersection = checkLineIntersection(com1_o.p0, cp1_2, com2_o.p, cp2_2, true)
|
|
@@ -203,7 +342,7 @@ export function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, de
|
|
|
203
342
|
//console.log('distS', distS, maxDist );
|
|
204
343
|
|
|
205
344
|
// not close enough - exit
|
|
206
|
-
if (distSplit > maxDist * tolerance
|
|
345
|
+
if (distSplit > maxDist * tolerance) {
|
|
207
346
|
//renderPoint(markers, ptSplit, 'cyan', '1%')
|
|
208
347
|
//renderPoint(markers, com1.p, 'red', '0.5%')
|
|
209
348
|
return commands;
|
|
@@ -241,7 +380,7 @@ export function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, de
|
|
|
241
380
|
if (areaDiff < 0.05 * tolerance) {
|
|
242
381
|
commands = [result];
|
|
243
382
|
//console.log('areaDiff', areaDiff);
|
|
244
|
-
}
|
|
383
|
+
}
|
|
245
384
|
}
|
|
246
385
|
|
|
247
386
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { checkLineIntersection, getAngle, getDistManhattan, getDistance, rotatePoint } from "./svgii/geometry";
|
|
1
|
+
import { checkLineIntersection, getAngle, getAngleFromDelta, getDistManhattan, getDistance, rotatePoint } from "./svgii/geometry";
|
|
2
2
|
import { getPathArea, getPolygonArea, getRelativeAreaDiff } from "./svgii/geometry_area";
|
|
3
|
+
import { renderPoint } from "./svgii/visualize";
|
|
3
4
|
//import { cubicCommandToArc } from "./svgii/pathData_convert";
|
|
4
5
|
|
|
5
6
|
export function pathDataCubicsToArc(pathData, { areaThreshold = 2.5 } = {}) {
|
|
@@ -14,7 +15,7 @@ export function pathDataCubicsToArc(pathData, { areaThreshold = 2.5 } = {}) {
|
|
|
14
15
|
let comAN = cubicCommandToArc(comN.p0, comN.cp1, comN.cp2, comN.p, areaThreshold)
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
if (comA.isArc
|
|
18
|
+
if (comA.isArc && comAN.isArc) {
|
|
18
19
|
|
|
19
20
|
let dist = getDistManhattan(p0, comN.p);
|
|
20
21
|
let maxDist = dist * 0.01;
|
|
@@ -30,8 +31,18 @@ export function pathDataCubicsToArc(pathData, { areaThreshold = 2.5 } = {}) {
|
|
|
30
31
|
let sweep = area < 0 ? 0 : 1;
|
|
31
32
|
|
|
32
33
|
if (vertical || horizontal) {
|
|
34
|
+
|
|
33
35
|
rx = Math.min(rx, comAN.rx)
|
|
34
36
|
ry = Math.min(ry, comAN.ry)
|
|
37
|
+
|
|
38
|
+
let diffR = Math.abs(rx - ry) / rx;
|
|
39
|
+
let isSemiCircle = diffR < 0.025
|
|
40
|
+
//console.log('isSemiCircle', isSemiCircle, diffR);
|
|
41
|
+
if (isSemiCircle) {
|
|
42
|
+
rx = rx > 1 ? 1 : Math.min(rx, ry);
|
|
43
|
+
ry = rx;
|
|
44
|
+
}
|
|
45
|
+
|
|
35
46
|
pathData[c] = null;
|
|
36
47
|
pathData[c + 1].type = 'A';
|
|
37
48
|
pathData[c + 1].values = [rx, ry, 0, 0, sweep, comN.p.x, comN.p.y];
|
|
@@ -58,40 +69,74 @@ export function cubicCommandToArc(p0, cp1, cp2, p, tolerance = 7.5) {
|
|
|
58
69
|
let arcSegArea = 0, isArc = false
|
|
59
70
|
|
|
60
71
|
// check angles
|
|
61
|
-
let
|
|
62
|
-
let
|
|
72
|
+
let dx1 = (cp1.x - p0.x)
|
|
73
|
+
let dy1 = (cp1.y - p0.y)
|
|
74
|
+
let dx2 = (cp2.x - p.x)
|
|
75
|
+
let dy2 = (cp2.y - p.y)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
//let angle1 = getAngle(p0, cp1, true);
|
|
79
|
+
//let angle2 = getAngle(p, cp2, true);
|
|
80
|
+
|
|
81
|
+
let thresh = getDistManhattan(p0, p) * 0.001
|
|
82
|
+
|
|
83
|
+
let isVertical1 = dx1 < thresh
|
|
84
|
+
let isVertical2 = dx2 < thresh
|
|
85
|
+
|
|
86
|
+
let isHorizontal1 = dy1 < thresh
|
|
87
|
+
let isHorizontal2 = dy2 < thresh
|
|
88
|
+
|
|
89
|
+
let isRightAngle = (isVertical1 || isVertical2) && (isHorizontal1 || isHorizontal2);
|
|
90
|
+
|
|
91
|
+
/*
|
|
92
|
+
let angle1 = getAngleFromDelta(dx1, dy1, true);
|
|
93
|
+
let angle2 = getAngleFromDelta(dx2, dy2, true);
|
|
63
94
|
let deltaAngle = Math.abs(angle1 - angle2) * 180 / Math.PI;
|
|
64
95
|
|
|
65
96
|
|
|
66
97
|
let angleDiff = Math.abs((deltaAngle % 180) - 90);
|
|
67
98
|
let isRightAngle = angleDiff < 3;
|
|
99
|
+
*/
|
|
68
100
|
|
|
69
101
|
|
|
70
102
|
let rx = 0
|
|
71
103
|
let ry = 0
|
|
72
|
-
let ptC;
|
|
104
|
+
let ptC = p0;
|
|
105
|
+
let r1 = 0, r2 = 0
|
|
73
106
|
|
|
74
107
|
if (isRightAngle) {
|
|
75
|
-
// point between cps
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// center point
|
|
79
|
-
let cp1_r = rotatePoint(cp1, p0.x, p0.y, (Math.PI * -0.5))
|
|
80
|
-
let cp2_r = rotatePoint(cp2, p.x, p.y, (Math.PI * 0.5))
|
|
81
|
-
|
|
82
|
-
// assumed centroid
|
|
83
|
-
ptC = checkLineIntersection(p0, cp1_r, p, cp2_r, false)
|
|
84
|
-
//renderPoint(markers, ptC, 'red', '1%', '0.5' )
|
|
85
|
-
|
|
86
|
-
|
|
87
108
|
|
|
109
|
+
if (isHorizontal1 && isVertical2) {
|
|
110
|
+
ptC = { x: p0.x, y: p.y }
|
|
111
|
+
r1 = Math.abs(p.x-p0.x)
|
|
112
|
+
r2 = Math.abs(p.y-p0.y)
|
|
113
|
+
}
|
|
114
|
+
else if (isHorizontal2 && isVertical1) {
|
|
115
|
+
ptC = { x: p.x, y: p0.y }
|
|
116
|
+
r2 = Math.abs(p0.x-p.x)
|
|
117
|
+
r1 = Math.abs(p0.y-p.y)
|
|
118
|
+
}
|
|
88
119
|
|
|
89
|
-
|
|
120
|
+
/*
|
|
121
|
+
if (r1 && r2) {
|
|
122
|
+
//console.log('r right', r1, r2);
|
|
123
|
+
}
|
|
124
|
+
//renderPoint(markers, ptC, 'red', '1%', '0.5')
|
|
125
|
+
//let pI = checkLineIntersection(p0, cp1, p, cp2, false);
|
|
126
|
+
*/
|
|
90
127
|
|
|
91
|
-
if (
|
|
128
|
+
if (r1 && r2) {
|
|
92
129
|
|
|
130
|
+
/*
|
|
93
131
|
let r1 = getDistance(p0, pI);
|
|
94
132
|
let r2 = getDistance(p, pI);
|
|
133
|
+
*/
|
|
134
|
+
|
|
135
|
+
//r1 = getDistance(p0, pI);
|
|
136
|
+
//let r12 = isHorizontal1 Math.abs(dx1);
|
|
137
|
+
//r2 = getDistance(p, pI);
|
|
138
|
+
//console.log('rEx', r1, r2);
|
|
139
|
+
|
|
95
140
|
|
|
96
141
|
let rMax = +Math.max(r1, r2).toFixed(8);
|
|
97
142
|
let rMin = +Math.min(r1, r2).toFixed(8);
|
|
@@ -109,7 +154,6 @@ export function cubicCommandToArc(p0, cp1, cp2, p, tolerance = 7.5) {
|
|
|
109
154
|
let circular = (100 / rx * Math.abs(rx - ry)) < 5;
|
|
110
155
|
|
|
111
156
|
if (circular) {
|
|
112
|
-
//rx = (rx+ry)/2
|
|
113
157
|
rx = rMax
|
|
114
158
|
ry = rx;
|
|
115
159
|
}
|
|
@@ -136,7 +180,7 @@ export function cubicCommandToArc(p0, cp1, cp2, p, tolerance = 7.5) {
|
|
|
136
180
|
arcSegArea = (Math.PI * (rx * ry)) / 4
|
|
137
181
|
|
|
138
182
|
// subtract polygon between start, end and center point
|
|
139
|
-
arcSegArea -= Math.abs(getPolygonArea([p0, p,
|
|
183
|
+
arcSegArea -= Math.abs(getPolygonArea([p0, p, ptC]))
|
|
140
184
|
|
|
141
185
|
let areaDiff = getRelativeAreaDiff(comArea, arcSegArea);
|
|
142
186
|
|